improve code design of ImportMetaPlugin

add missing evaluation for compare operations
refactor duplicate code

add unhandledExpressionMemberChain hook to parser
add evaluateToNumber and types to JavascriptParserHelpers
add undefined to BasicEvaluatedExpression

add import.meta.webpack as webpack major version
This commit is contained in:
Tobias Koppers 2020-07-06 17:13:09 +02:00
parent 001090fade
commit 85edc5c7fa
6 changed files with 386 additions and 292 deletions

View File

@ -6,18 +6,28 @@
"use strict";
const { pathToFileURL } = require("url");
const ModuleDependencyWarning = require("../ModuleDependencyWarning");
const Template = require("../Template");
const BasicEvaluatedExpression = require("../javascript/BasicEvaluatedExpression");
const {
evaluateToIdentifier,
toConstantDependency
toConstantDependency,
evaluateToString,
evaluateToNumber
} = require("../javascript/JavascriptParserHelpers");
const memorize = require("../util/memorize");
const propertyAccess = require("../util/propertyAccess");
const ConstDependency = require("./ConstDependency");
const CriticalDependencyWarning = require("./CriticalDependencyWarning");
/** @typedef {import("estree").MemberExpression} MemberExpression */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../NormalModule")} NormalModule */
/** @typedef {import("../javascript/JavascriptParser")} Parser */
const getCriticalDependencyWarning = memorize(() =>
require("./CriticalDependencyWarning")
);
class ImportMetaPlugin {
/**
* @param {Compiler} compiler compiler
@ -39,6 +49,7 @@ class ImportMetaPlugin {
* @returns {void}
*/
const parserHandler = (parser, parserOptions) => {
/// import.meta direct ///
parser.hooks.typeof
.for("import.meta")
.tap(
@ -47,35 +58,33 @@ class ImportMetaPlugin {
);
parser.hooks.metaProperty.tap(
"ImportMetaPlugin",
(metaProperty, end) => {
if (end !== undefined && end !== metaProperty.range[1]) {
const dep = new ConstDependency("undefined", [
metaProperty.range[0],
end
]);
dep.loc = metaProperty.loc;
return parser.state.module.addPresentationalDependency(dep);
}
parser.state.module.addWarning(
new CriticalDependencyWarning(
"Direct accessing import.meta is disallowed"
)
);
const dep = new ConstDependency("({})", metaProperty.range);
dep.loc = metaProperty.loc;
parser.state.module.addPresentationalDependency(dep);
}
toConstantDependency(parser, "Object()")
);
parser.hooks.metaProperty.tap("ImportMetaPlugin", metaProperty => {
const CriticalDependencyWarning = getCriticalDependencyWarning();
parser.state.module.addWarning(
new ModuleDependencyWarning(
parser.state.module,
new CriticalDependencyWarning(
"Accessing import.meta directly is unsupported (only property access is supported)"
),
metaProperty.loc
)
);
const dep = new ConstDependency("Object()", metaProperty.range);
dep.loc = metaProperty.loc;
parser.state.module.addPresentationalDependency(dep);
return true;
});
parser.hooks.evaluateTypeof
.for("import.meta")
.tap("ImportMetaPlugin", evaluateToString("object"));
parser.hooks.evaluateIdentifier.for("import.meta").tap(
"ImportMetaPlugin",
evaluateToIdentifier(
"import.meta",
"import.meta",
() => ["url"],
true
)
evaluateToIdentifier("import.meta", "import.meta", () => [], true)
);
/// import.meta.url ///
parser.hooks.typeof
.for("import.meta.url")
.tap(
@ -93,6 +102,9 @@ class ImportMetaPlugin {
parser.state.module.addPresentationalDependency(dep);
return true;
});
parser.hooks.evaluateTypeof
.for("import.meta.url")
.tap("ImportMetaPlugin", evaluateToString("string"));
parser.hooks.evaluateIdentifier
.for("import.meta.url")
.tap("ImportMetaPlugin", expr => {
@ -100,6 +112,59 @@ class ImportMetaPlugin {
.setString(getUrl(parser.state.module))
.setRange(expr.range);
});
/// import.meta.webpack ///
const webpackVersion = parseInt(
require("../../package.json").version,
10
);
parser.hooks.typeof
.for("import.meta.webpack")
.tap(
"ImportMetaPlugin",
toConstantDependency(parser, JSON.stringify("number"))
);
parser.hooks.expression
.for("import.meta.webpack")
.tap(
"ImportMetaPlugin",
toConstantDependency(parser, JSON.stringify(webpackVersion))
);
parser.hooks.evaluateTypeof
.for("import.meta.webpack")
.tap("ImportMetaPlugin", evaluateToString("number"));
parser.hooks.evaluateIdentifier
.for("import.meta.webpack")
.tap("ImportMetaPlugin", evaluateToNumber(webpackVersion));
/// Unknown properties ///
parser.hooks.unhandledExpressionMemberChain
.for("import.meta")
.tap("ImportMetaPlugin", (expr, members) => {
const dep = new ConstDependency(
`${Template.toNormalComment(
"unsupported import.meta." + members.join(".")
)} undefined${propertyAccess(members, 1)}`,
expr.range
);
dep.loc = expr.loc;
parser.state.module.addPresentationalDependency(dep);
return true;
});
parser.hooks.evaluate
.for("MemberExpression")
.tap("ImportMetaPlugin", expression => {
const expr = /** @type {MemberExpression} */ (expression);
if (
expr.object.type === "MetaProperty" &&
expr.property.type ===
(expr.computed ? "Literal" : "Identifier")
) {
return new BasicEvaluatedExpression()
.setUndefined()
.setRange(expr.range);
}
});
};
normalModuleFactory.hooks.parser

View File

@ -6,18 +6,19 @@
"use strict";
const TypeUnknown = 0;
const TypeNull = 1;
const TypeString = 2;
const TypeNumber = 3;
const TypeBoolean = 4;
const TypeRegExp = 5;
const TypeConditional = 6;
const TypeArray = 7;
const TypeConstArray = 8;
const TypeIdentifier = 9;
const TypeWrapped = 10;
const TypeTemplateString = 11;
const TypeBigInt = 12;
const TypeUndefined = 1;
const TypeNull = 2;
const TypeString = 3;
const TypeNumber = 4;
const TypeBoolean = 5;
const TypeRegExp = 6;
const TypeConditional = 7;
const TypeArray = 8;
const TypeConstArray = 9;
const TypeIdentifier = 10;
const TypeWrapped = 11;
const TypeTemplateString = 12;
const TypeBigInt = 13;
class BasicEvaluatedExpression {
constructor() {
@ -48,6 +49,10 @@ class BasicEvaluatedExpression {
return this.type === TypeNull;
}
isUndefined() {
return this.type === TypeUndefined;
}
isString() {
return this.type === TypeString;
}
@ -105,6 +110,7 @@ class BasicEvaluatedExpression {
if (this.falsy) return false;
if (this.isBoolean()) return this.bool;
if (this.isNull()) return false;
if (this.isUndefined()) return false;
if (this.isString()) return this.string !== "";
if (this.isNumber()) return this.number !== 0;
if (this.isBigInt()) return this.bigint !== BigInt(0);
@ -127,6 +133,7 @@ class BasicEvaluatedExpression {
asString() {
if (this.isBoolean()) return `${this.bool}`;
if (this.isNull()) return "null";
if (this.isUndefined()) return "undefined";
if (this.isString()) return this.string;
if (this.isNumber()) return `${this.number}`;
if (this.isBigInt()) return `${this.bigint}`;
@ -159,6 +166,11 @@ class BasicEvaluatedExpression {
return this;
}
setUndefined() {
this.type = TypeUndefined;
return this;
}
setNull() {
this.type = TypeNull;
return this;

View File

@ -260,14 +260,18 @@ class JavascriptParser extends Parser {
),
/** @type {HookMap<SyncBailHook<[ExpressionNode], boolean | void>>} */
new: new HookMap(() => new SyncBailHook(["expression"])),
/** @type {SyncBailHook<[MetaPropertyNode, number], boolean | void>} */
metaProperty: new SyncBailHook(["metaProperty", "expressionEnd"]),
/** @type {SyncBailHook<[MetaPropertyNode], boolean | void>} */
metaProperty: new SyncBailHook(["metaProperty"]),
/** @type {HookMap<SyncBailHook<[ExpressionNode], boolean | void>>} */
expression: new HookMap(() => new SyncBailHook(["expression"])),
/** @type {HookMap<SyncBailHook<[ExpressionNode, string[]], boolean | void>>} */
expressionMemberChain: new HookMap(
() => new SyncBailHook(["expression", "members"])
),
/** @type {HookMap<SyncBailHook<[ExpressionNode, string[]], boolean | void>>} */
unhandledExpressionMemberChain: new HookMap(
() => new SyncBailHook(["expression", "members"])
),
/** @type {SyncBailHook<[ExpressionNode], boolean | void>} */
expressionConditionalOperator: new SyncBailHook(["expression"]),
/** @type {SyncBailHook<[ExpressionNode], boolean | void>} */
@ -352,6 +356,45 @@ class JavascriptParser extends Parser {
.tap("JavascriptParser", _expr => {
const expr = /** @type {BinaryExpressionNode} */ (_expr);
const handleNumberOperation = fn => {
const left = this.evaluateExpression(expr.left);
const right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (left.isNumber() && right.isNumber()) {
res = new BasicEvaluatedExpression();
res.setNumber(fn(left.number, right.number));
res.setRange(expr.range);
return res;
} else if (left.isBigInt() && right.isBigInt()) {
res = new BasicEvaluatedExpression();
res.setBigInt(fn(left.bigint, right.bigint));
res.setRange(expr.range);
return res;
}
};
const handleCompare = fn => {
const left = this.evaluateExpression(expr.left);
const right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (left.isNumber() && right.isNumber()) {
res = new BasicEvaluatedExpression();
res.setBoolean(fn(left.number, right.number));
res.setRange(expr.range);
return res;
} else if (left.isString() && right.isString()) {
res = new BasicEvaluatedExpression();
res.setBoolean(fn(left.string, right.string));
res.setRange(expr.range);
return res;
} else if (left.isBigInt() && right.isBigInt()) {
res = new BasicEvaluatedExpression();
res.setBoolean(fn(left.bigint, right.bigint));
res.setRange(expr.range);
return res;
}
};
let left;
let right;
let res;
@ -491,66 +534,13 @@ class JavascriptParser extends Parser {
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()) {
res = new BasicEvaluatedExpression();
res.setNumber(left.number - right.number);
res.setRange(expr.range);
return res;
} else if (left.isBigInt() && right.isBigInt()) {
res = new BasicEvaluatedExpression();
res.setBigInt(left.bigint - right.bigint);
res.setRange(expr.range);
return res;
}
return handleNumberOperation((l, r) => l - r);
} else if (expr.operator === "*") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (left.isNumber() && right.isNumber()) {
res = new BasicEvaluatedExpression();
res.setNumber(left.number * right.number);
res.setRange(expr.range);
return res;
} else if (left.isBigInt() && right.isBigInt()) {
res = new BasicEvaluatedExpression();
res.setBigInt(left.bigint * right.bigint);
res.setRange(expr.range);
return res;
}
return handleNumberOperation((l, r) => l * r);
} else if (expr.operator === "/") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (left.isNumber() && right.isNumber()) {
res = new BasicEvaluatedExpression();
res.setNumber(left.number / right.number);
res.setRange(expr.range);
return res;
} else if (left.isBigInt() && right.isBigInt()) {
res = new BasicEvaluatedExpression();
res.setBigInt(left.bigint / right.bigint);
res.setRange(expr.range);
return res;
}
return handleNumberOperation((l, r) => l / r);
} else if (expr.operator === "**") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (left.isNumber() && right.isNumber()) {
res = new BasicEvaluatedExpression();
res.setNumber(left.number ** right.number);
res.setRange(expr.range);
return res;
} else if (left.isBigInt() && right.isBigInt()) {
res = new BasicEvaluatedExpression();
res.setBigInt(left.bigint ** right.bigint);
res.setRange(expr.range);
return res;
}
return handleNumberOperation((l, r) => l ** r);
} else if (expr.operator === "==" || expr.operator === "===") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
@ -586,53 +576,14 @@ class JavascriptParser extends Parser {
return res.setBoolean(false);
}
} else if (expr.operator === "&") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (left.isNumber() && right.isNumber()) {
res = new BasicEvaluatedExpression();
res.setNumber(left.number & right.number);
res.setRange(expr.range);
return res;
} else if (left.isBigInt() && right.isBigInt()) {
res = new BasicEvaluatedExpression();
res.setBigInt(left.bigint & right.bigint);
res.setRange(expr.range);
return res;
}
return handleNumberOperation((l, r) => l & r);
} else if (expr.operator === "|") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (left.isNumber() && right.isNumber()) {
res = new BasicEvaluatedExpression();
res.setNumber(left.number | right.number);
res.setRange(expr.range);
return res;
} else if (left.isBigInt() && right.isBigInt()) {
res = new BasicEvaluatedExpression();
res.setBigInt(left.bigint | right.bigint);
res.setRange(expr.range);
return res;
}
return handleNumberOperation((l, r) => l | r);
} else if (expr.operator === "^") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (left.isNumber() && right.isNumber()) {
res = new BasicEvaluatedExpression();
res.setNumber(left.number ^ right.number);
res.setRange(expr.range);
return res;
} else if (left.isBigInt() && right.isBigInt()) {
res = new BasicEvaluatedExpression();
res.setBigInt(left.bigint ^ right.bigint);
res.setRange(expr.range);
return res;
}
return handleNumberOperation((l, r) => l ^ r);
} else if (expr.operator === ">>>") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
const left = this.evaluateExpression(expr.left);
const right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (!left.isNumber() || !right.isNumber()) return;
res = new BasicEvaluatedExpression();
@ -640,35 +591,17 @@ class JavascriptParser extends Parser {
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()) {
res = new BasicEvaluatedExpression();
res.setNumber(left.number >> right.number);
res.setRange(expr.range);
return res;
} else if (left.isBigInt() && right.isBigInt()) {
res = new BasicEvaluatedExpression();
res.setBigInt(left.bigint >> right.bigint);
res.setRange(expr.range);
return res;
}
return handleNumberOperation((l, r) => l >> r);
} else if (expr.operator === "<<") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (left.isNumber() && right.isNumber()) {
res = new BasicEvaluatedExpression();
res.setNumber(left.number << right.number);
res.setRange(expr.range);
return res;
} else if (left.isBigInt() && right.isBigInt()) {
res = new BasicEvaluatedExpression();
res.setBigInt(left.bigint << right.bigint);
res.setRange(expr.range);
return res;
}
return handleNumberOperation((l, r) => l << r);
} else if (expr.operator === "<") {
return handleCompare((l, r) => l < r);
} else if (expr.operator === ">") {
return handleCompare((l, r) => l > r);
} else if (expr.operator === "<=") {
return handleCompare((l, r) => l <= r);
} else if (expr.operator === ">=") {
return handleCompare((l, r) => l >= r);
}
});
this.hooks.evaluate
@ -677,26 +610,39 @@ class JavascriptParser extends Parser {
const expr = /** @type {UnaryExpressionNode} */ (_expr);
if (expr.operator === "typeof") {
if (expr.argument.type === "Identifier") {
const res = this.callHooksForName(
this.hooks.evaluateTypeof,
expr.argument.name,
expr
);
if (res !== undefined) return res;
}
if (expr.argument.type === "MemberExpression") {
const res = this.callHooksForExpression(
this.hooks.evaluateTypeof,
expr.argument,
expr
);
if (res !== undefined) return res;
}
if (expr.argument.type === "FunctionExpression") {
return new BasicEvaluatedExpression()
.setString("function")
.setRange(expr.range);
switch (expr.argument.type) {
case "Identifier": {
const res = this.callHooksForName(
this.hooks.evaluateTypeof,
expr.argument.name,
expr
);
if (res !== undefined) return res;
break;
}
case "MetaProperty": {
const res = this.callHooksForName(
this.hooks.evaluateTypeof,
"import.meta",
expr
);
if (res !== undefined) return res;
break;
}
case "MemberExpression": {
const res = this.callHooksForExpression(
this.hooks.evaluateTypeof,
expr.argument,
expr
);
if (res !== undefined) return res;
break;
}
case "FunctionExpression": {
return new BasicEvaluatedExpression()
.setString("function")
.setRange(expr.range);
}
}
const arg = this.evaluateExpression(expr.argument);
if (arg.isString() || arg.isWrapped()) {
@ -709,6 +655,11 @@ class JavascriptParser extends Parser {
.setString("object")
.setRange(expr.range);
}
if (arg.isUndefined()) {
return new BasicEvaluatedExpression()
.setString("undefined")
.setRange(expr.range);
}
if (arg.isNumber()) {
return new BasicEvaluatedExpression()
.setString("number")
@ -732,36 +683,16 @@ class JavascriptParser extends Parser {
} 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.isNull()) {
return new BasicEvaluatedExpression()
.setBoolean(true)
.setRange(expr.range);
}
if (argument.isTruthy()) {
const bool = argument.asBool();
if (bool === true) {
return new BasicEvaluatedExpression()
.setBoolean(false)
.setRange(expr.range);
}
if (argument.isFalsy()) {
} else if (bool === false) {
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;
@ -800,76 +731,85 @@ class JavascriptParser extends Parser {
.setString("undefined")
.setRange(expr.range);
});
this.hooks.evaluate.for("Identifier").tap("JavascriptParser", _expr => {
const expr = /** @type {IdentifierNode} */ (_expr);
/** @typedef {{ name: string | VariableInfo, rootInfo: string | VariableInfo, getMembers: () => string[] }} GetInfoResult */
/**
* @param {string} exprType expression type name
* @param {function(ExpressionNode): GetInfoResult | undefined} getInfo get info
* @returns {void}
*/
const tapEvaluateWithVariableInfo = (exprType, getInfo) => {
/** @type {ExpressionNode | undefined} */
let cachedExpression = undefined;
/** @type {GetInfoResult | undefined} */
let cachedInfo = undefined;
this.hooks.evaluate.for(exprType).tap("JavascriptParser", expr => {
const expression = /** @type {MemberExpressionNode} */ (expr);
return this.callHooksForNameWithFallback(
this.hooks.evaluateIdentifier,
expr.name,
name =>
new BasicEvaluatedExpression()
.setIdentifier(name, this.getVariableInfo(expr.name), () => [])
.setRange(expr.range),
() => {
const hook = this.hooks.evaluateDefinedIdentifier.get(expr.name);
if (hook !== undefined) {
return hook.call(expr);
const info = getInfo(expr);
if (info !== undefined) {
return this.callHooksForInfoWithFallback(
this.hooks.evaluateIdentifier,
info.name,
name => {
cachedExpression = expression;
cachedInfo = info;
},
name => {
const hook = this.hooks.evaluateDefinedIdentifier.get(name);
if (hook !== undefined) {
return hook.call(expression);
}
},
expression
);
}
});
this.hooks.evaluate
.for(exprType)
.tap({ name: "JavascriptParser", stage: 100 }, expr => {
const info = cachedExpression === expr ? cachedInfo : getInfo(expr);
if (info !== undefined) {
return new BasicEvaluatedExpression()
.setIdentifier(info.name, info.rootInfo, info.getMembers)
.setRange(expr.range);
}
},
expr
});
};
tapEvaluateWithVariableInfo("Identifier", expr => {
const info = this.getVariableInfo(
/** @type {IdentifierNode} */ (expr).name
);
if (
typeof info === "string" ||
(info instanceof VariableInfo && typeof info.freeName === "string")
) {
return { name: info, rootInfo: info, getMembers: () => [] };
}
});
this.hooks.evaluate.for("ThisExpression").tap("JavascriptParser", _expr => {
const expr = /** @type {ThisExpressionNode} */ (_expr);
return this.callHooksForNameWithFallback(
this.hooks.evaluateIdentifier,
"this",
name =>
new BasicEvaluatedExpression()
.setIdentifier(name, this.getVariableInfo("this"), () => [])
.setRange(expr.range),
() => {
const hook = this.hooks.evaluateDefinedIdentifier.get("this");
if (hook !== undefined) {
return hook.call(expr);
}
},
expr
);
tapEvaluateWithVariableInfo("ThisExpression", expr => {
const info = this.getVariableInfo("this");
if (
typeof info === "string" ||
(info instanceof VariableInfo && typeof info.freeName === "string")
) {
return { name: info, rootInfo: info, getMembers: () => [] };
}
});
this.hooks.evaluate.for("MetaProperty").tap("JavascriptParser", expr => {
const metaProperty = /** @type {MetaPropertyNode} */ (expr);
return this.callHooksForNameWithFallback(
return this.callHooksForName(
this.hooks.evaluateIdentifier,
"import.meta",
undefined,
undefined,
metaProperty
);
});
this.hooks.evaluate
.for("MemberExpression")
.tap("JavascriptParser", expr => {
const expression = /** @type {MemberExpressionNode} */ (expr);
tapEvaluateWithVariableInfo("MemberExpression", expr =>
this.getMemberExpressionInfo(/** @type {MemberExpressionNode} */ (expr), [
"expression"
])
);
return this.callHooksForExpressionWithFallback(
this.hooks.evaluateIdentifier,
expression,
(name, rootInfo, getMembers) =>
new BasicEvaluatedExpression()
.setIdentifier(name, rootInfo, getMembers)
.setRange(expression.range),
name => {
const hook = this.hooks.evaluateDefinedIdentifier.get(name);
if (hook !== undefined) {
return hook.call(expression);
}
},
expression
);
});
this.hooks.evaluate.for("CallExpression").tap("JavascriptParser", _expr => {
const expr = /** @type {CallExpressionNode} */ (_expr);
if (
@ -894,6 +834,30 @@ class JavascriptParser extends Parser {
return hook.call(expr, param);
}
});
this.hooks.evaluateCallExpressionMember
.for("indexOf")
.tap("JavascriptParser", (expr, param) => {
if (!param.isString()) return;
if (expr.arguments.length === 0) return;
const [arg1, arg2] = expr.arguments;
if (arg1.type === "SpreadElement") return;
const arg1Eval = this.evaluateExpression(arg1);
if (!arg1Eval.isString()) return;
const arg1Value = arg1Eval.string;
let result;
if (arg2) {
if (arg2.type === "SpreadElement") return;
const arg2Eval = this.evaluateExpression(arg2);
if (!arg2Eval.isNumber()) return;
result = param.string.indexOf(arg1Value, arg2Eval.number);
} else {
result = param.string.indexOf(arg1Value);
}
return new BasicEvaluatedExpression()
.setNumber(result)
.setRange(expr.range);
});
this.hooks.evaluateCallExpressionMember
.for("replace")
.tap("JavascriptParser", (expr, param) => {
@ -1816,10 +1780,7 @@ class JavascriptParser extends Parser {
// renaming with "var a = b;"
const hook = this.hooks.rename.get(renameIdentifier);
if (hook === undefined || !hook.call(declarator.init)) {
this.setVariable(
declarator.id.name,
this.getVariableInfo(renameIdentifier)
);
this.setVariable(declarator.id.name, renameIdentifier);
}
break;
}
@ -2408,7 +2369,14 @@ class JavascriptParser extends Parser {
expression,
exprInfo.name,
exprInfo.rootInfo,
members
members.slice(),
() =>
this.callHooksForInfo(
this.hooks.unhandledExpressionMemberChain,
exprInfo.rootInfo,
expression,
members
)
);
return;
}
@ -2438,7 +2406,7 @@ class JavascriptParser extends Parser {
name,
rootInfo,
members,
end
onUnhandled
) {
const result = this.callHooksForInfo(
this.hooks.expression,
@ -2458,11 +2426,9 @@ class JavascriptParser extends Parser {
name,
rootInfo,
members,
end || expression.range[1]
onUnhandled
);
} else if (expression.object.type === "MetaProperty") {
this.walkMetaProperty(expression.object, end || expression.range[1]);
} else {
} else if (!onUnhandled || !onUnhandled()) {
this.walkExpression(expression.object);
}
if (expression.computed === true) this.walkExpression(expression.property);
@ -2478,10 +2444,9 @@ class JavascriptParser extends Parser {
/**
* @param {MetaPropertyNode} metaProperty meta property
* @param {number=} end member chain end range
*/
walkMetaProperty(metaProperty, end) {
this.hooks.metaProperty.call(metaProperty, end);
walkMetaProperty(metaProperty) {
this.hooks.metaProperty.call(metaProperty);
}
callHooksForExpression(hookMap, expr, ...args) {

View File

@ -9,6 +9,15 @@ const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning");
const ConstDependency = require("../dependencies/ConstDependency");
const BasicEvaluatedExpression = require("./BasicEvaluatedExpression");
/** @typedef {import("estree").Expression} Expression */
/** @typedef {import("./JavascriptParser")} JavascriptParser */
/**
* @param {JavascriptParser} parser the parser
* @param {string} value the const value
* @param {string[]=} runtimeRequirements runtime requirements
* @returns {function(Expression): true} plugin function
*/
exports.toConstantDependency = (parser, value, runtimeRequirements) => {
return function constDependency(expr) {
const dep = new ConstDependency(value, expr.range, runtimeRequirements);
@ -18,12 +27,30 @@ exports.toConstantDependency = (parser, value, runtimeRequirements) => {
};
};
/**
* @param {string} value the string value
* @returns {function(Expression): BasicEvaluatedExpression} plugin function
*/
exports.evaluateToString = value => {
return function stringExpression(expr) {
return new BasicEvaluatedExpression().setString(value).setRange(expr.range);
};
};
/**
* @param {number} value the number value
* @returns {function(Expression): BasicEvaluatedExpression} plugin function
*/
exports.evaluateToNumber = value => {
return function stringExpression(expr) {
return new BasicEvaluatedExpression().setNumber(value).setRange(expr.range);
};
};
/**
* @param {boolean} value the boolean value
* @returns {function(Expression): BasicEvaluatedExpression} plugin function
*/
exports.evaluateToBoolean = value => {
return function booleanExpression(expr) {
return new BasicEvaluatedExpression()

View File

@ -1,26 +1,46 @@
const { pathToFileURL } = require("url");
const url =
pathToFileURL(
require("path").resolve("./test/cases/esm/import-meta/index.js")
).toString();
const url = pathToFileURL(
require("path").resolve("./test/cases/esm/import-meta/index.js")
).toString();
const webpackVersion = parseInt(
require("../../../../package.json").version,
10
);
it("typeof import.meta === \"object\"", () => {
it('typeof import.meta === "object"', () => {
expect(typeof import.meta).toBe("object");
if (typeof import.meta !== "object") require("fail");
});
it("typeof import.meta.url === \"string\"", () => {
it('typeof import.meta.url === "string"', () => {
expect(typeof import.meta.url).toBe("string");
if (typeof import.meta.url !== "string") require("fail");
});
it('typeof import.meta.webpack === "number"', () => {
expect(typeof import.meta.webpack).toBe("number");
if (typeof import.meta.webpack !== "number") require("fail");
});
it("should return correct import.meta.url", () => {
expect(import.meta.url).toBe(url);
expect(import.meta["url"]).toBe(url);
expect("my" + import.meta.url).toBe("my" + url);
if (import.meta.url.indexOf("index.js") === -1) require("fail");
});
it("should return correct import.meta", () => {
expect(import.meta["url"]).toBe(url);
it("should return correct import.meta.webpack", () => {
expect(import.meta.webpack).toBe(webpackVersion);
if (import.meta.webpack < 5) require("fail");
if (import.meta.webpack >= 5) {
} else {
require("fail");
}
});
it("should return undefined for unknown property", () => {
expect(import.meta.other).toBe(undefined);
if (typeof import.meta.other !== "undefined") require("fail");
expect(() => import.meta.other.other.other).toThrowError();
// if (typeof import.meta.other.other.other !== "undefined") require("fail");
});

11
types.d.ts vendored
View File

@ -333,6 +333,7 @@ declare abstract class BasicEvaluatedExpression {
getMembers: any;
expression: any;
isNull(): boolean;
isUndefined(): boolean;
isString(): boolean;
isNumber(): boolean;
isBigInt(): boolean;
@ -349,6 +350,7 @@ declare abstract class BasicEvaluatedExpression {
asBool(): any;
asString(): any;
setString(string?: any): BasicEvaluatedExpression;
setUndefined(): BasicEvaluatedExpression;
setNull(): BasicEvaluatedExpression;
setNumber(number?: any): BasicEvaluatedExpression;
setBigInt(bigint?: any): BasicEvaluatedExpression;
@ -3184,11 +3186,14 @@ declare abstract class JavascriptParser extends Parser {
>
>;
new: HookMap<SyncBailHook<[Expression], boolean | void>>;
metaProperty: SyncBailHook<[MetaProperty, number], boolean | void>;
metaProperty: SyncBailHook<[MetaProperty], boolean | void>;
expression: HookMap<SyncBailHook<[Expression], boolean | void>>;
expressionMemberChain: HookMap<
SyncBailHook<[Expression, string[]], boolean | void>
>;
unhandledExpressionMemberChain: HookMap<
SyncBailHook<[Expression, string[]], boolean | void>
>;
expressionConditionalOperator: SyncBailHook<[Expression], boolean | void>;
expressionLogicalOperator: SyncBailHook<[Expression], boolean | void>;
program: SyncBailHook<[Program, Comment[]], boolean | void>;
@ -3292,11 +3297,11 @@ declare abstract class JavascriptParser extends Parser {
name?: any,
rootInfo?: any,
members?: any,
end?: any
onUnhandled?: any
): void;
walkThisExpression(expression?: any): void;
walkIdentifier(expression?: any): void;
walkMetaProperty(metaProperty: MetaProperty, end?: number): void;
walkMetaProperty(metaProperty: MetaProperty): void;
callHooksForExpression(hookMap: any, expr: any, ...args: any[]): any;
callHooksForExpressionWithFallback<T, R>(
hookMap: HookMap<SyncBailHook<T, R>>,