mirror of https://github.com/webpack/webpack.git
refactor equality comparison, support nullish coalescing in ConstPlugin
- add handleStrictEqualityComparison callback - add handleAbstractEqualityComparison callback - rework evaluateIdentifier interface - add tests
This commit is contained in:
parent
3ecc87889c
commit
5ec7dfd6ac
|
@ -314,6 +314,45 @@ class ConstPlugin {
|
|||
dep.loc = expression.loc;
|
||||
parser.state.module.addPresentationalDependency(dep);
|
||||
}
|
||||
return keepRight;
|
||||
}
|
||||
} else if (expression.operator === "??") {
|
||||
const param = parser.evaluateExpression(expression.left);
|
||||
const keepRight = param && param.asNullish();
|
||||
if (typeof keepRight === "boolean") {
|
||||
// ------------------------------------------
|
||||
//
|
||||
// Given the following code:
|
||||
//
|
||||
// (falsy || truthy) ?? someExpression();
|
||||
//
|
||||
// the generated code is:
|
||||
//
|
||||
// (falsy || truthy) ?? false;
|
||||
//
|
||||
// ------------------------------------------
|
||||
//
|
||||
// Given the following code:
|
||||
//
|
||||
// nullable ?? someExpression();
|
||||
//
|
||||
// the generated code is:
|
||||
//
|
||||
// null ?? someExpression();
|
||||
//
|
||||
if (keepRight) {
|
||||
const dep = new ConstDependency("null", param.range);
|
||||
dep.loc = expression.loc;
|
||||
parser.state.module.addPresentationalDependency(dep);
|
||||
} else {
|
||||
const dep = new ConstDependency(
|
||||
"false",
|
||||
expression.right.range
|
||||
);
|
||||
dep.loc = expression.loc;
|
||||
parser.state.module.addPresentationalDependency(dep);
|
||||
}
|
||||
|
||||
return keepRight;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -199,13 +199,7 @@ class CommonJsPlugin {
|
|||
|
||||
parser.hooks.evaluateIdentifier.for("module.hot").tap(
|
||||
"CommonJsPlugin",
|
||||
evaluateToIdentifier(
|
||||
"module.hot",
|
||||
"module",
|
||||
() => ["hot"],
|
||||
false,
|
||||
true
|
||||
)
|
||||
evaluateToIdentifier("module.hot", "module", () => ["hot"], null)
|
||||
);
|
||||
|
||||
new CommonJsImportsParserPlugin(options).apply(parser);
|
||||
|
|
|
@ -98,6 +98,37 @@ class BasicEvaluatedExpression {
|
|||
return this.type === TypeTemplateString;
|
||||
}
|
||||
|
||||
/**
|
||||
* check for "simple" types (inlined) only
|
||||
* @returns {boolean} is simple type
|
||||
*/
|
||||
isSimpleType() {
|
||||
switch (this.type) {
|
||||
case TypeIdentifier:
|
||||
case TypeConditional:
|
||||
case TypeWrapped:
|
||||
case TypeUnknown:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BasicEvaluatedExpression} basicEvaluatedExpression basicEvaluatedExpression
|
||||
* @returns {boolean|undefined} is same type
|
||||
*/
|
||||
isSameType(basicEvaluatedExpression) {
|
||||
if (
|
||||
this.type === TypeUnknown ||
|
||||
basicEvaluatedExpression.type === TypeUnknown
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.type === basicEvaluatedExpression.type;
|
||||
}
|
||||
|
||||
isTruthy() {
|
||||
return this.truthy;
|
||||
}
|
||||
|
@ -112,7 +143,7 @@ class BasicEvaluatedExpression {
|
|||
|
||||
asBool() {
|
||||
if (this.truthy) return true;
|
||||
if (this.falsy) return false;
|
||||
if (this.falsy || this.nullish) return false;
|
||||
if (this.isBoolean()) return this.bool;
|
||||
if (this.isNull()) return false;
|
||||
if (this.isUndefined()) return false;
|
||||
|
|
|
@ -383,21 +383,24 @@ class JavascriptParser extends Parser {
|
|||
}
|
||||
};
|
||||
|
||||
const handleCompare = fn => {
|
||||
const handleStrictCompare = fn => {
|
||||
const left = this.evaluateExpression(expr.left);
|
||||
const right = this.evaluateExpression(expr.right);
|
||||
if (!left || !right) return;
|
||||
if (left.isNumber() && right.isNumber()) {
|
||||
const sameType = left.isSameType(right);
|
||||
if (sameType !== true) return;
|
||||
|
||||
if (left.isNumber()) {
|
||||
res = new BasicEvaluatedExpression();
|
||||
res.setBoolean(fn(left.number, right.number));
|
||||
res.setRange(expr.range);
|
||||
return res;
|
||||
} else if (left.isString() && right.isString()) {
|
||||
} else if (left.isString()) {
|
||||
res = new BasicEvaluatedExpression();
|
||||
res.setBoolean(fn(left.string, right.string));
|
||||
res.setRange(expr.range);
|
||||
return res;
|
||||
} else if (left.isBigInt() && right.isBigInt()) {
|
||||
} else if (left.isBigInt()) {
|
||||
res = new BasicEvaluatedExpression();
|
||||
res.setBoolean(fn(left.bigint, right.bigint));
|
||||
res.setRange(expr.range);
|
||||
|
@ -405,6 +408,112 @@ class JavascriptParser extends Parser {
|
|||
}
|
||||
};
|
||||
|
||||
const handleStrictEqualityComparison = eql => {
|
||||
left = this.evaluateExpression(expr.left);
|
||||
right = this.evaluateExpression(expr.right);
|
||||
if (!left || !right) return;
|
||||
const sameType = left.isSameType(right);
|
||||
if (sameType === undefined) return;
|
||||
res = new BasicEvaluatedExpression();
|
||||
res.setRange(expr.range);
|
||||
|
||||
if (sameType === true) {
|
||||
// check only for types that could be compared
|
||||
if (left.isString()) {
|
||||
return res.setBoolean(
|
||||
eql
|
||||
? left.string === right.string
|
||||
: left.string !== right.string
|
||||
);
|
||||
} else if (left.isNumber()) {
|
||||
return res.setBoolean(
|
||||
eql
|
||||
? left.number === right.number
|
||||
: left.number !== right.number
|
||||
);
|
||||
} else if (left.isBigInt()) {
|
||||
return res.setBoolean(
|
||||
eql
|
||||
? left.bigint === right.bigint
|
||||
: left.bigint !== right.bigint
|
||||
);
|
||||
} else if (left.isBoolean()) {
|
||||
return res.setBoolean(
|
||||
eql ? left.bool === right.bool : left.bool !== right.bool
|
||||
);
|
||||
} else if (left.isNull() || left.isUndefined()) {
|
||||
return res.setBoolean(eql);
|
||||
}
|
||||
}
|
||||
|
||||
// check for "simple" types, e.g.
|
||||
// if ([] === 1)
|
||||
// if ([] === [])
|
||||
// if (0 === null)
|
||||
// if ("" === /a/i)
|
||||
if (left.isSimpleType() && right.isSimpleType()) {
|
||||
return res.setBoolean(!eql);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAbstractEqualityComparison = eql => {
|
||||
left = this.evaluateExpression(expr.left);
|
||||
right = this.evaluateExpression(expr.right);
|
||||
if (!left || !right) return;
|
||||
const sameType = left.isSameType(right);
|
||||
if (sameType === undefined) return;
|
||||
res = new BasicEvaluatedExpression();
|
||||
res.setRange(expr.range);
|
||||
|
||||
// use strict equality comparison
|
||||
if (sameType === true) {
|
||||
if (left.isString()) {
|
||||
return res.setBoolean(
|
||||
eql
|
||||
? left.string === right.string
|
||||
: left.string !== right.string
|
||||
);
|
||||
} else if (left.isNumber()) {
|
||||
return res.setBoolean(
|
||||
eql
|
||||
? left.number === right.number
|
||||
: left.number !== right.number
|
||||
);
|
||||
} else if (left.isBigInt()) {
|
||||
return res.setBoolean(
|
||||
eql
|
||||
? left.bigint === right.bigint
|
||||
: left.bigint !== right.bigint
|
||||
);
|
||||
} else if (left.isBoolean()) {
|
||||
return res.setBoolean(
|
||||
eql ? left.bool === right.bool : left.bool !== right.bool
|
||||
);
|
||||
} else if (left.isNull() || left.isUndefined()) {
|
||||
return res.setBoolean(eql);
|
||||
// if ([] == [])
|
||||
// if ("a,s".split(",") == ["a", "s"])
|
||||
// if (/a/ == /a/)
|
||||
} else if (
|
||||
left.isArray() ||
|
||||
left.isConstArray() ||
|
||||
left.isRegExp()
|
||||
) {
|
||||
return res.setBoolean(!eql);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(left.isFalsy() && right.isTruthy()) ||
|
||||
(left.isTruthy() && right.isFalsy())
|
||||
) {
|
||||
return res.setBoolean(!eql);
|
||||
}
|
||||
|
||||
// abstract equality comparison is not fully implemented
|
||||
return undefined;
|
||||
};
|
||||
|
||||
let left;
|
||||
let right;
|
||||
let res;
|
||||
|
@ -551,40 +660,14 @@ class JavascriptParser extends Parser {
|
|||
return handleNumberOperation((l, r) => l / r);
|
||||
} else if (expr.operator === "**") {
|
||||
return handleNumberOperation((l, r) => l ** r);
|
||||
} 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.isBigInt() && right.isBigInt()) {
|
||||
return res.setBoolean(left.bigint === right.bigint);
|
||||
} else if (left.isBoolean() && right.isBoolean()) {
|
||||
return res.setBoolean(left.bool === right.bool);
|
||||
} else if (left.isNull() && right.isNull()) {
|
||||
return res.setBoolean(true);
|
||||
}
|
||||
} 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.isBigInt() && right.isBigInt()) {
|
||||
return res.setBoolean(left.bigint !== right.bigint);
|
||||
} else if (left.isBoolean() && right.isBoolean()) {
|
||||
return res.setBoolean(left.bool !== right.bool);
|
||||
} else if (left.isNull() && right.isNull()) {
|
||||
return res.setBoolean(false);
|
||||
}
|
||||
} else if (expr.operator === "===") {
|
||||
return handleStrictEqualityComparison(true);
|
||||
} else if (expr.operator === "==") {
|
||||
return handleAbstractEqualityComparison(true);
|
||||
} else if (expr.operator === "!==") {
|
||||
return handleStrictEqualityComparison(false);
|
||||
} else if (expr.operator === "!=") {
|
||||
return handleAbstractEqualityComparison(false);
|
||||
} else if (expr.operator === "&") {
|
||||
return handleNumberOperation((l, r) => l & r);
|
||||
} else if (expr.operator === "|") {
|
||||
|
@ -605,13 +688,13 @@ class JavascriptParser extends Parser {
|
|||
} else if (expr.operator === "<<") {
|
||||
return handleNumberOperation((l, r) => l << r);
|
||||
} else if (expr.operator === "<") {
|
||||
return handleCompare((l, r) => l < r);
|
||||
return handleStrictCompare((l, r) => l < r);
|
||||
} else if (expr.operator === ">") {
|
||||
return handleCompare((l, r) => l > r);
|
||||
return handleStrictCompare((l, r) => l > r);
|
||||
} else if (expr.operator === "<=") {
|
||||
return handleCompare((l, r) => l <= r);
|
||||
return handleStrictCompare((l, r) => l <= r);
|
||||
} else if (expr.operator === ">=") {
|
||||
return handleCompare((l, r) => l >= r);
|
||||
return handleStrictCompare((l, r) => l >= r);
|
||||
}
|
||||
});
|
||||
this.hooks.evaluate
|
||||
|
|
|
@ -63,28 +63,26 @@ exports.evaluateToBoolean = value => {
|
|||
* @param {string} identifier identifier
|
||||
* @param {string} rootInfo rootInfo
|
||||
* @param {function(): string[]} getMembers getMembers
|
||||
* @param {boolean=} truthy is truthy
|
||||
* @param {boolean=} nullish is nullish
|
||||
* @param {boolean|null=} truthy is truthy, null if nullish
|
||||
* @returns {function(ExpressionNode): BasicEvaluatedExpression} callback
|
||||
*/
|
||||
exports.evaluateToIdentifier = (
|
||||
identifier,
|
||||
rootInfo,
|
||||
getMembers,
|
||||
truthy,
|
||||
nullish
|
||||
) => {
|
||||
exports.evaluateToIdentifier = (identifier, rootInfo, getMembers, truthy) => {
|
||||
return function identifierExpression(expr) {
|
||||
let evaluatedExpression = new BasicEvaluatedExpression()
|
||||
.setIdentifier(identifier, rootInfo, getMembers)
|
||||
.setRange(expr.range);
|
||||
if (truthy === true) {
|
||||
evaluatedExpression.setTruthy();
|
||||
} else if (truthy === false) {
|
||||
evaluatedExpression.setFalsy();
|
||||
}
|
||||
if (nullish !== undefined) {
|
||||
evaluatedExpression.setNullish(nullish);
|
||||
switch (truthy) {
|
||||
case true:
|
||||
evaluatedExpression.setTruthy();
|
||||
evaluatedExpression.setNullish(false);
|
||||
break;
|
||||
case null:
|
||||
evaluatedExpression.setFalsy();
|
||||
evaluatedExpression.setNullish(true);
|
||||
break;
|
||||
case false:
|
||||
evaluatedExpression.setFalsy();
|
||||
break;
|
||||
}
|
||||
|
||||
return evaluatedExpression;
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
it("should evaluate null", function() {
|
||||
var y = null ? require("fail") : require("./a");
|
||||
const y = null ? require("fail") : require("./a");
|
||||
if(null)
|
||||
require("fail");
|
||||
});
|
||||
|
||||
it("should evaluate logical expression", function() {
|
||||
var value1 = "hello" || require("fail");
|
||||
var value2 = typeof require === "function" || require("fail");
|
||||
var value3 = "" && require("fail");
|
||||
var value4 = typeof require !== "function" && require("fail");
|
||||
var value5 = "hello" && (() => "value5")();
|
||||
var value6 = "" || (() => "value6")();
|
||||
var value7 = (function () { return'value7'===typeof 'value7'&&'value7'})();
|
||||
const value1 = "hello" || require("fail");
|
||||
const value2 = typeof require === "function" || require("fail");
|
||||
const value3 = "" && require("fail");
|
||||
const value4 = typeof require !== "function" && require("fail");
|
||||
const value5 = "hello" && (() => "value5")();
|
||||
const value6 = "" || (() => "value6")();
|
||||
const value7 = (function () { return'value7'===typeof 'value7'&&'value7'})();
|
||||
const value8 = [] != [] || require("fail");
|
||||
const value9 = (null === 1) && require("fail");
|
||||
const value91 = [] === [] && require("fail");
|
||||
const value92 = /a/ === /a/ && require("fail");
|
||||
|
||||
expect(value1).toBe("hello");
|
||||
expect(value2).toBe(true);
|
||||
|
@ -20,32 +24,46 @@ it("should evaluate logical expression", function() {
|
|||
expect(value5).toBe("value5");
|
||||
expect(value6).toBe("value6");
|
||||
expect(value7).toBe(false);
|
||||
expect(value8).toBe(true);
|
||||
expect(value9).toBe(false);
|
||||
expect(value91).toBe(false);
|
||||
expect(value92).toBe(false);
|
||||
|
||||
if (!process.version.startsWith("v14")) return;
|
||||
|
||||
const value10 = "" ?? require("fail");
|
||||
const value11 = null ?? "expected";
|
||||
const value12 = ("" ?? require("fail")) && true;
|
||||
|
||||
expect(value10).toBe("");
|
||||
expect(value11).toBe("expected");
|
||||
expect(value12).toBe("")
|
||||
});
|
||||
|
||||
if("shouldn't evaluate expression", function() {
|
||||
var value = "";
|
||||
var x = (value + "") ? "fail" : "ok";
|
||||
it("shouldn't evaluate expression", function() {
|
||||
const value = "";
|
||||
const x = (value + "") ? "fail" : "ok";
|
||||
expect(x).toBe("ok");
|
||||
});
|
||||
|
||||
it("should short-circuit evaluating", function() {
|
||||
var expr;
|
||||
var a = false && expr ? require("fail") : require("./a");
|
||||
var b = true || expr ? require("./a") : require("fail");
|
||||
let expr;
|
||||
const a = false && expr ? require("fail") : require("./a");
|
||||
const b = true || expr ? require("./a") : require("fail");
|
||||
});
|
||||
|
||||
it("should evaluate __dirname and __resourceQuery with replace and substr", function() {
|
||||
var result = require("./resourceQuery/index?" + __dirname);
|
||||
const result = require("./resourceQuery/index?" + __dirname);
|
||||
expect(result).toEqual("?resourceQuery");
|
||||
});
|
||||
|
||||
it("should evaluate __dirname and __resourceFragment with replace and substr", function() {
|
||||
var result = require("./resourceFragment/index#" + __dirname);
|
||||
const result = require("./resourceFragment/index#" + __dirname);
|
||||
expect(result).toEqual("#resourceFragment");
|
||||
});
|
||||
|
||||
it("should allow resourceFragment in context", function() {
|
||||
var fn = x => require(`./resourceFragment/${x}#..`);
|
||||
const fn = x => require(`./resourceFragment/${x}#..`);
|
||||
expect(fn("index")).toEqual("#resourceFragment");
|
||||
expect(fn("returnRF")).toBe("#..")
|
||||
});
|
||||
|
|
|
@ -348,6 +348,12 @@ declare abstract class BasicEvaluatedExpression {
|
|||
isIdentifier(): boolean;
|
||||
isWrapped(): boolean;
|
||||
isTemplateString(): boolean;
|
||||
|
||||
/**
|
||||
* check for "simple" types (inlined) only
|
||||
*/
|
||||
isSimpleType(): boolean;
|
||||
isSameType(basicEvaluatedExpression: BasicEvaluatedExpression): boolean;
|
||||
isTruthy(): boolean;
|
||||
isFalsy(): boolean;
|
||||
isNullish(): any;
|
||||
|
|
Loading…
Reference in New Issue