From dfdc554b80056ab64a0ff2cf7a6357baea7d4149 Mon Sep 17 00:00:00 2001 From: Ivan Kopeykin Date: Thu, 23 Jul 2020 15:38:40 +0300 Subject: [PATCH 1/5] add optional chaining support --- lib/javascript/JavascriptParser.js | 49 +++++++++++++++++++ .../parsing/optional-chaining/index.js | 10 ++++ .../optional-chaining/webpack.config.js | 18 +++++++ .../parsing/hot-api-optional-chaining/a.js | 1 + .../parsing/hot-api-optional-chaining/b.js | 1 + .../hot-api-optional-chaining/index.js | 7 +++ types.d.ts | 1 + 7 files changed, 87 insertions(+) create mode 100644 test/configCases/parsing/optional-chaining/index.js create mode 100644 test/configCases/parsing/optional-chaining/webpack.config.js create mode 100644 test/hotCases/parsing/hot-api-optional-chaining/a.js create mode 100644 test/hotCases/parsing/hot-api-optional-chaining/b.js create mode 100644 test/hotCases/parsing/hot-api-optional-chaining/index.js diff --git a/lib/javascript/JavascriptParser.js b/lib/javascript/JavascriptParser.js index c2dd7f83451..059701f6c99 100644 --- a/lib/javascript/JavascriptParser.js +++ b/lib/javascript/JavascriptParser.js @@ -29,6 +29,7 @@ const BasicEvaluatedExpression = require("./BasicEvaluatedExpression"); /** @typedef {import("estree").LabeledStatement} LabeledStatementNode */ /** @typedef {import("estree").Literal} LiteralNode */ /** @typedef {import("estree").LogicalExpression} LogicalExpressionNode */ +/** @typedef {import("estree").ChainExpression} ChainExpressionNode */ /** @typedef {import("estree").MemberExpression} MemberExpressionNode */ /** @typedef {import("estree").MetaProperty} MetaPropertyNode */ /** @typedef {import("estree").MethodDefinition} MethodDefinitionNode */ @@ -1223,6 +1224,40 @@ class JavascriptParser extends Parser { .setItems(items) .setRange(expr.range); }); + this.hooks.evaluate + .for("ChainExpression") + .tap("JavascriptParser", _expr => { + const expr = /** @type {ChainExpressionNode} */ (_expr); + const result = this.evaluateExpression(expr.expression); + if (result) return result; + + /** @type {ExpressionNode|SuperNode} */ + let next; + + if (expr.expression.type === "CallExpression") { + next = expr.expression.callee; + } else { + next = expr.expression.object; + } + + while (next.type === "MemberExpression") { + while (next.type === "MemberExpression" && !next.optional) { + next = next.object; + } + + const result = this.evaluateExpression(expr.expression); + if (result) { + result.setRange(_expr.range); + if (result.isNull()) result.setUndefined(); + + return result; + } + } + + return new BasicEvaluatedExpression() + .setRange(_expr.range) + .setUndefined(); + }); } getRenameIdentifier(expr) { @@ -2024,6 +2059,9 @@ class JavascriptParser extends Parser { case "CallExpression": this.walkCallExpression(expression); break; + case "ChainExpression": + this.walkChainExpression(expression); + break; case "ClassExpression": this.walkClassExpression(expression); break; @@ -2326,6 +2364,17 @@ class JavascriptParser extends Parser { this.walkClass(expression); } + /** + * @param {ChainExpressionNode} expression expression + */ + walkChainExpression(expression) { + if (expression.expression.type === "CallExpression") { + this.walkCallExpression(expression.expression); + } else { + this.walkMemberExpression(expression.expression); + } + } + _walkIIFE(functionExpression, options, currentThis) { const getVarInfo = argOrThis => { const renameIdentifier = this.getRenameIdentifier(argOrThis); diff --git a/test/configCases/parsing/optional-chaining/index.js b/test/configCases/parsing/optional-chaining/index.js new file mode 100644 index 00000000000..722f4931c7f --- /dev/null +++ b/test/configCases/parsing/optional-chaining/index.js @@ -0,0 +1,10 @@ +it("should correctly render defined data #1", () => { + expect(_VALUE_?._DEFINED_).toBe(1); +}); + +it("should correctly render defined data #2", () => { + const val1 = _VALUE_?._PROP_?._DEFINED_; + const val2 = _VALUE_?._PROP_?._UNDEFINED_; + expect(val1).toBe(2); + expect(val2).toBeUndefined(); +}); diff --git a/test/configCases/parsing/optional-chaining/webpack.config.js b/test/configCases/parsing/optional-chaining/webpack.config.js new file mode 100644 index 00000000000..e805aa39e04 --- /dev/null +++ b/test/configCases/parsing/optional-chaining/webpack.config.js @@ -0,0 +1,18 @@ +const { DefinePlugin } = require("../../../../"); + +/** @type {import("../../../../").Configuration} */ +module.exports = { + mode: "development", + devtool: false, + target: "web", + plugins: [ + new DefinePlugin({ + _VALUE_: { + _DEFINED_: 1, + _PROP_: { + _DEFINED_: 2 + } + } + }) + ] +}; diff --git a/test/hotCases/parsing/hot-api-optional-chaining/a.js b/test/hotCases/parsing/hot-api-optional-chaining/a.js new file mode 100644 index 00000000000..01cd3e7139e --- /dev/null +++ b/test/hotCases/parsing/hot-api-optional-chaining/a.js @@ -0,0 +1 @@ +module.exports = "a"; \ No newline at end of file diff --git a/test/hotCases/parsing/hot-api-optional-chaining/b.js b/test/hotCases/parsing/hot-api-optional-chaining/b.js new file mode 100644 index 00000000000..fba3204fe66 --- /dev/null +++ b/test/hotCases/parsing/hot-api-optional-chaining/b.js @@ -0,0 +1 @@ +module.exports = "b"; \ No newline at end of file diff --git a/test/hotCases/parsing/hot-api-optional-chaining/index.js b/test/hotCases/parsing/hot-api-optional-chaining/index.js new file mode 100644 index 00000000000..beaaa950b4b --- /dev/null +++ b/test/hotCases/parsing/hot-api-optional-chaining/index.js @@ -0,0 +1,7 @@ +it("should run module.hot.accept(…)", function() { + module?.hot?.accept("./a", function() {}); +}); + +it("should skip rest members", function() { + module?.hot.accept(); +}); diff --git a/types.d.ts b/types.d.ts index 720c5d2c713..99f86b1d860 100644 --- a/types.d.ts +++ b/types.d.ts @@ -3562,6 +3562,7 @@ declare abstract class JavascriptParser extends Parser { walkTemplateLiteral(expression?: any): void; walkTaggedTemplateExpression(expression?: any): void; walkClassExpression(expression?: any): void; + walkChainExpression(expression: ChainExpression): void; walkImportExpression(expression?: any): void; walkCallExpression(expression?: any, args?: any): void; walkMemberExpression(expression?: any): void; From 18ae4d16d84b467947eddccf6524b0b87a190817 Mon Sep 17 00:00:00 2001 From: Ivan Kopeykin Date: Sun, 19 Jul 2020 22:14:57 +0300 Subject: [PATCH 2/5] add evaluation to optional chaining --- lib/ConstPlugin.js | 47 +++++++++++++++++++ lib/javascript/JavascriptParser.js | 37 ++++++++++----- test/cases/parsing/optional-chaining/a.js | 1 + test/cases/parsing/optional-chaining/index.js | 5 ++ types.d.ts | 1 + 5 files changed, 78 insertions(+), 13 deletions(-) create mode 100644 test/cases/parsing/optional-chaining/a.js create mode 100644 test/cases/parsing/optional-chaining/index.js diff --git a/lib/ConstPlugin.js b/lib/ConstPlugin.js index 8cc8e74f701..7cea721ced5 100644 --- a/lib/ConstPlugin.js +++ b/lib/ConstPlugin.js @@ -10,6 +10,8 @@ const ConstDependency = require("./dependencies/ConstDependency"); const { evaluateToString } = require("./javascript/JavascriptParserHelpers"); const { parseResource } = require("./util/identifier"); +/** @typedef {import("estree").Expression} ExpressionNode */ +/** @typedef {import("estree").Super} SuperNode */ /** @typedef {import("./Compiler")} Compiler */ const collectDeclaration = (declarations, pattern) => { @@ -372,6 +374,51 @@ class ConstPlugin { } } ); + parser.hooks.optionalChaining.tap("ConstPlugin", expr => { + const optionalExpressionsStack = []; + /** @type {ExpressionNode|SuperNode} */ + let next; + + if (expr.expression.type === "CallExpression") { + next = expr.expression.callee; + } else { + next = expr.expression; + } + + while (next.type === "MemberExpression") { + if (next.optional) { + optionalExpressionsStack.push(next.object); + } + next = next.object; + } + + while (optionalExpressionsStack.length) { + const expression = optionalExpressionsStack.pop(); + const evaluated = parser.evaluateExpression(expression); + + if (evaluated && evaluated.asNullish()) { + // ------------------------------------------ + // + // Given the following code: + // + // nullishMemberChain?.a.b(); + // + // the generated code is: + // + // null; // or undefined; if evaluated to undefined + // + // ------------------------------------------ + // + const dep = new ConstDependency( + evaluated.isUndefined() ? "undefined" : "null", + expr.range + ); + dep.loc = expr.loc; + parser.state.module.addPresentationalDependency(dep); + return true; + } + } + }); parser.hooks.evaluateIdentifier .for("__resourceQuery") .tap("ConstPlugin", expr => { diff --git a/lib/javascript/JavascriptParser.js b/lib/javascript/JavascriptParser.js index 059701f6c99..f512e700020 100644 --- a/lib/javascript/JavascriptParser.js +++ b/lib/javascript/JavascriptParser.js @@ -261,6 +261,8 @@ class JavascriptParser extends Parser { "members" ]) ), + /** @type {SyncBailHook<[ChainExpressionNode], boolean | void>} */ + optionalChaining: new SyncBailHook(["optionalChaining"]), /** @type {HookMap>} */ new: new HookMap(() => new SyncBailHook(["expression"])), /** @type {SyncBailHook<[MetaPropertyNode], boolean | void>} */ @@ -1228,9 +1230,11 @@ class JavascriptParser extends Parser { .for("ChainExpression") .tap("JavascriptParser", _expr => { const expr = /** @type {ChainExpressionNode} */ (_expr); - const result = this.evaluateExpression(expr.expression); + let result = this.evaluateExpression(expr.expression); if (result) return result; + /** @type {ExpressionNode[]} */ + const stack = []; /** @type {ExpressionNode|SuperNode} */ let next; @@ -1241,22 +1245,25 @@ class JavascriptParser extends Parser { } while (next.type === "MemberExpression") { - while (next.type === "MemberExpression" && !next.optional) { - next = next.object; + if (next.optional) { + stack.push(/** @type {ExpressionNode} */ (next.object)); } - const result = this.evaluateExpression(expr.expression); - if (result) { - result.setRange(_expr.range); - if (result.isNull()) result.setUndefined(); + next = next.object; + } - return result; + while (stack.length) { + const expression = stack.pop(); + const evaluated = this.evaluateExpression(expression); + + if (evaluated && evaluated.asNullish()) { + return evaluated.setRange(_expr.range); } } return new BasicEvaluatedExpression() .setRange(_expr.range) - .setUndefined(); + .setExpression(_expr); }); } @@ -2368,10 +2375,14 @@ class JavascriptParser extends Parser { * @param {ChainExpressionNode} expression expression */ walkChainExpression(expression) { - if (expression.expression.type === "CallExpression") { - this.walkCallExpression(expression.expression); - } else { - this.walkMemberExpression(expression.expression); + const result = this.hooks.optionalChaining.call(expression); + + if (result === undefined) { + if (expression.expression.type === "CallExpression") { + this.walkCallExpression(expression.expression); + } else { + this.walkMemberExpression(expression.expression); + } } } diff --git a/test/cases/parsing/optional-chaining/a.js b/test/cases/parsing/optional-chaining/a.js new file mode 100644 index 00000000000..bd816eaba4c --- /dev/null +++ b/test/cases/parsing/optional-chaining/a.js @@ -0,0 +1 @@ +module.exports = 1; diff --git a/test/cases/parsing/optional-chaining/index.js b/test/cases/parsing/optional-chaining/index.js new file mode 100644 index 00000000000..fb84be9985b --- /dev/null +++ b/test/cases/parsing/optional-chaining/index.js @@ -0,0 +1,5 @@ +it("should handle optional members", () => { + expect( + module.hot?.accept((() => {throw new Error("fail")})()) + ).toBe(null); +}); diff --git a/types.d.ts b/types.d.ts index 99f86b1d860..4fa34d18d12 100644 --- a/types.d.ts +++ b/types.d.ts @@ -3458,6 +3458,7 @@ declare abstract class JavascriptParser extends Parser { boolean | void > >; + optionalChaining: SyncBailHook<[ChainExpression], boolean | void>; new: HookMap>; metaProperty: SyncBailHook<[MetaProperty], boolean | void>; expression: HookMap>; From 70c0b28d00aaebcff8ba4769dd217172e16cc316 Mon Sep 17 00:00:00 2001 From: Ivan Kopeykin Date: Sun, 26 Jul 2020 20:05:21 +0300 Subject: [PATCH 3/5] add tests, refactor how optional chaining handles --- lib/ConstPlugin.js | 40 +++++++++------- lib/javascript/JavascriptParser.js | 46 ++++++++++--------- test/cases/parsing/optional-chaining/a.js | 1 - test/cases/parsing/optional-chaining/index.js | 18 ++++++-- .../parsing/optional-chaining/test.filter.js | 12 +++++ .../parsing/optional-chaining/test.filter.js | 5 ++ test/helpers/supportsNullishCoalescing.js | 2 +- test/helpers/supportsOptionalChaining.js | 8 ++++ .../parsing/hot-api-optional-chaining/b.js | 1 - .../hot-api-optional-chaining/index.js | 3 -- 10 files changed, 88 insertions(+), 48 deletions(-) create mode 100644 test/cases/parsing/optional-chaining/test.filter.js create mode 100644 test/configCases/parsing/optional-chaining/test.filter.js create mode 100644 test/helpers/supportsOptionalChaining.js delete mode 100644 test/hotCases/parsing/hot-api-optional-chaining/b.js diff --git a/lib/ConstPlugin.js b/lib/ConstPlugin.js index 7cea721ced5..844da0c6fa3 100644 --- a/lib/ConstPlugin.js +++ b/lib/ConstPlugin.js @@ -375,21 +375,32 @@ class ConstPlugin { } ); parser.hooks.optionalChaining.tap("ConstPlugin", expr => { + /** @type {ExpressionNode[]} */ const optionalExpressionsStack = []; /** @type {ExpressionNode|SuperNode} */ - let next; + let next = expr.expression; - if (expr.expression.type === "CallExpression") { - next = expr.expression.callee; - } else { - next = expr.expression; - } - - while (next.type === "MemberExpression") { - if (next.optional) { - optionalExpressionsStack.push(next.object); + while ( + next.type === "MemberExpression" || + next.type === "CallExpression" + ) { + if (next.type === "MemberExpression") { + if (next.optional) { + // SuperNode can not be optional + optionalExpressionsStack.push( + /** @type {ExpressionNode} */ (next.object) + ); + } + next = next.object; + } else { + if (next.optional) { + // SuperNode can not be optional + optionalExpressionsStack.push( + /** @type {ExpressionNode} */ (next.callee) + ); + } + next = next.callee; } - next = next.object; } while (optionalExpressionsStack.length) { @@ -405,14 +416,11 @@ class ConstPlugin { // // the generated code is: // - // null; // or undefined; if evaluated to undefined + // undefined; // // ------------------------------------------ // - const dep = new ConstDependency( - evaluated.isUndefined() ? "undefined" : "null", - expr.range - ); + const dep = new ConstDependency(" undefined", expr.range); dep.loc = expr.loc; parser.state.module.addPresentationalDependency(dep); return true; diff --git a/lib/javascript/JavascriptParser.js b/lib/javascript/JavascriptParser.js index f512e700020..f60bf00205b 100644 --- a/lib/javascript/JavascriptParser.js +++ b/lib/javascript/JavascriptParser.js @@ -1230,40 +1230,42 @@ class JavascriptParser extends Parser { .for("ChainExpression") .tap("JavascriptParser", _expr => { const expr = /** @type {ChainExpressionNode} */ (_expr); - let result = this.evaluateExpression(expr.expression); - if (result) return result; - /** @type {ExpressionNode[]} */ - const stack = []; + const optionalExpressionsStack = []; /** @type {ExpressionNode|SuperNode} */ - let next; - - if (expr.expression.type === "CallExpression") { - next = expr.expression.callee; - } else { - next = expr.expression.object; - } + let next = expr.expression; - while (next.type === "MemberExpression") { - if (next.optional) { - stack.push(/** @type {ExpressionNode} */ (next.object)); + while ( + next.type === "MemberExpression" || + next.type === "CallExpression" + ) { + if (next.type === "MemberExpression") { + if (next.optional) { + // SuperNode can not be optional + optionalExpressionsStack.push( + /** @type {ExpressionNode} */ (next.object) + ); + } + next = next.object; + } else { + if (next.optional) { + // SuperNode can not be optional + optionalExpressionsStack.push( + /** @type {ExpressionNode} */ (next.callee) + ); + } + next = next.callee; } - - next = next.object; } - while (stack.length) { - const expression = stack.pop(); + while (optionalExpressionsStack.length > 0) { + const expression = optionalExpressionsStack.pop(); const evaluated = this.evaluateExpression(expression); if (evaluated && evaluated.asNullish()) { return evaluated.setRange(_expr.range); } } - - return new BasicEvaluatedExpression() - .setRange(_expr.range) - .setExpression(_expr); }); } diff --git a/test/cases/parsing/optional-chaining/a.js b/test/cases/parsing/optional-chaining/a.js index bd816eaba4c..e69de29bb2d 100644 --- a/test/cases/parsing/optional-chaining/a.js +++ b/test/cases/parsing/optional-chaining/a.js @@ -1 +0,0 @@ -module.exports = 1; diff --git a/test/cases/parsing/optional-chaining/index.js b/test/cases/parsing/optional-chaining/index.js index fb84be9985b..cff0a3dbb5b 100644 --- a/test/cases/parsing/optional-chaining/index.js +++ b/test/cases/parsing/optional-chaining/index.js @@ -1,5 +1,15 @@ -it("should handle optional members", () => { - expect( - module.hot?.accept((() => {throw new Error("fail")})()) - ).toBe(null); +it("should evaluate optional members", () => { + if (!module.hot) { + expect( + module.hot?.accept((() => {throw new Error("fail")})()) + ).toBe(undefined); + } +}); + +it("should evaluate optional chaining as a part of statement", () => { + if (module.hot?.accept) { + module.hot?.accept("./a.js"); + } else { + expect(module.hot).toBe(undefined); + } }); diff --git a/test/cases/parsing/optional-chaining/test.filter.js b/test/cases/parsing/optional-chaining/test.filter.js new file mode 100644 index 00000000000..0f4b7f9625d --- /dev/null +++ b/test/cases/parsing/optional-chaining/test.filter.js @@ -0,0 +1,12 @@ +const supportsOptionalChaining = require("../../../helpers/supportsOptionalChaining"); + +/** + * @param {import("../../../../").Configuration} config + * @returns {boolean} + */ +module.exports = function (config) { + if (config.mode === "production") return false; + if (config.optimization && config.optimization.minimizer) return false; + + return supportsOptionalChaining(); +}; diff --git a/test/configCases/parsing/optional-chaining/test.filter.js b/test/configCases/parsing/optional-chaining/test.filter.js new file mode 100644 index 00000000000..698f2822d2d --- /dev/null +++ b/test/configCases/parsing/optional-chaining/test.filter.js @@ -0,0 +1,5 @@ +var supportsOptionalChaining = require("../../../helpers/supportsOptionalChaining"); + +module.exports = function (config) { + return supportsOptionalChaining(); +}; diff --git a/test/helpers/supportsNullishCoalescing.js b/test/helpers/supportsNullishCoalescing.js index b88b1fb5dd0..38dfb6d548c 100644 --- a/test/helpers/supportsNullishCoalescing.js +++ b/test/helpers/supportsNullishCoalescing.js @@ -1,4 +1,4 @@ -module.exports = function supportsObjectDestructuring() { +module.exports = function supportsNullishCoalescing() { try { var f = eval("(function f() { return null ?? true; })"); return f(); diff --git a/test/helpers/supportsOptionalChaining.js b/test/helpers/supportsOptionalChaining.js new file mode 100644 index 00000000000..bd9df3d7fb9 --- /dev/null +++ b/test/helpers/supportsOptionalChaining.js @@ -0,0 +1,8 @@ +module.exports = function supportsOptionalChaining() { + try { + var f = eval("(function f() { return ({a: true}) ?.a })"); + return f(); + } catch (e) { + return false; + } +}; diff --git a/test/hotCases/parsing/hot-api-optional-chaining/b.js b/test/hotCases/parsing/hot-api-optional-chaining/b.js deleted file mode 100644 index fba3204fe66..00000000000 --- a/test/hotCases/parsing/hot-api-optional-chaining/b.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = "b"; \ No newline at end of file diff --git a/test/hotCases/parsing/hot-api-optional-chaining/index.js b/test/hotCases/parsing/hot-api-optional-chaining/index.js index beaaa950b4b..a1f5a1f79ff 100644 --- a/test/hotCases/parsing/hot-api-optional-chaining/index.js +++ b/test/hotCases/parsing/hot-api-optional-chaining/index.js @@ -1,7 +1,4 @@ it("should run module.hot.accept(…)", function() { module?.hot?.accept("./a", function() {}); -}); - -it("should skip rest members", function() { module?.hot.accept(); }); From 1eacb07c95f77b4dc719ff5d54ba65e9d3463efc Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Fri, 7 Aug 2020 14:32:26 +0200 Subject: [PATCH 4/5] improve optional chaining handling --- lib/ConstPlugin.js | 5 ++++- lib/javascript/BasicEvaluatedExpression.js | 1 + lib/javascript/JavascriptParser.js | 18 ++++++++++++++++++ test/TestCasesProduction.test.js | 3 ++- .../parsing/optional-chaining/test.filter.js | 9 +-------- .../parsing/optional-chaining/index.js | 18 ++++++++++++++++++ .../parsing/hot-api-optional-chaining/a.js | 4 +++- .../parsing/hot-api-optional-chaining/index.js | 14 +++++++++++--- 8 files changed, 58 insertions(+), 14 deletions(-) diff --git a/lib/ConstPlugin.js b/lib/ConstPlugin.js index 844da0c6fa3..21fbaf99a69 100644 --- a/lib/ConstPlugin.js +++ b/lib/ConstPlugin.js @@ -420,7 +420,10 @@ class ConstPlugin { // // ------------------------------------------ // - const dep = new ConstDependency(" undefined", expr.range); + const dep = new ConstDependency( + evaluated.isNull() ? " null" : " undefined", + expr.range + ); dep.loc = expr.loc; parser.state.module.addPresentationalDependency(dep); return true; diff --git a/lib/javascript/BasicEvaluatedExpression.js b/lib/javascript/BasicEvaluatedExpression.js index 4d8f98713c4..821d8d0de06 100644 --- a/lib/javascript/BasicEvaluatedExpression.js +++ b/lib/javascript/BasicEvaluatedExpression.js @@ -405,6 +405,7 @@ class BasicEvaluatedExpression { setTruthy() { this.falsy = false; this.truthy = true; + this.nullish = false; return this; } diff --git a/lib/javascript/JavascriptParser.js b/lib/javascript/JavascriptParser.js index f60bf00205b..b7d13198d91 100644 --- a/lib/javascript/JavascriptParser.js +++ b/lib/javascript/JavascriptParser.js @@ -758,6 +758,15 @@ class JavascriptParser extends Parser { if (res !== undefined) return res; break; } + case "ChainExpression": { + const res = this.callHooksForExpression( + this.hooks.evaluateTypeof, + expr.argument.expression, + expr + ); + if (res !== undefined) return res; + break; + } case "FunctionExpression": { return new BasicEvaluatedExpression() .setString("function") @@ -1266,6 +1275,7 @@ class JavascriptParser extends Parser { return evaluated.setRange(_expr.range); } } + return this.evaluateExpression(expr.expression); }); } @@ -2229,6 +2239,14 @@ class JavascriptParser extends Parser { expression ); if (result === true) return; + if (expression.argument.type === "ChainExpression") { + const result = this.callHooksForExpression( + this.hooks.typeof, + expression.argument.expression, + expression + ); + if (result === true) return; + } } this.walkExpression(expression.argument); } diff --git a/test/TestCasesProduction.test.js b/test/TestCasesProduction.test.js index 8708f34d2a4..4e60190fa86 100644 --- a/test/TestCasesProduction.test.js +++ b/test/TestCasesProduction.test.js @@ -3,6 +3,7 @@ const { describeCases } = require("./TestCases.template"); describe("TestCases", () => { describeCases({ name: "production", - mode: "production" + mode: "production", + minimize: true }); }); diff --git a/test/cases/parsing/optional-chaining/test.filter.js b/test/cases/parsing/optional-chaining/test.filter.js index 0f4b7f9625d..a5caf1901f1 100644 --- a/test/cases/parsing/optional-chaining/test.filter.js +++ b/test/cases/parsing/optional-chaining/test.filter.js @@ -1,12 +1,5 @@ const supportsOptionalChaining = require("../../../helpers/supportsOptionalChaining"); -/** - * @param {import("../../../../").Configuration} config - * @returns {boolean} - */ module.exports = function (config) { - if (config.mode === "production") return false; - if (config.optimization && config.optimization.minimizer) return false; - - return supportsOptionalChaining(); + return !config.minimize && supportsOptionalChaining(); }; diff --git a/test/configCases/parsing/optional-chaining/index.js b/test/configCases/parsing/optional-chaining/index.js index 722f4931c7f..17d45b973be 100644 --- a/test/configCases/parsing/optional-chaining/index.js +++ b/test/configCases/parsing/optional-chaining/index.js @@ -5,6 +5,24 @@ it("should correctly render defined data #1", () => { it("should correctly render defined data #2", () => { const val1 = _VALUE_?._PROP_?._DEFINED_; const val2 = _VALUE_?._PROP_?._UNDEFINED_; + const val3 = typeof _VALUE_?._PROP_?._DEFINED_; + const val4 = typeof _VALUE_?._PROP_?._UNDEFINED_; + const val5 = _VALUE_?._PROP_; + const val6 = typeof _VALUE_?._PROP_; expect(val1).toBe(2); expect(val2).toBeUndefined(); + expect(val3).toBe("number"); + expect(val4).toBe("undefined"); + expect(val5).toEqual({ _DEFINED_: 2 }); + expect(val6).toBe("object"); + expect((() => typeof _VALUE_?._PROP_?._DEFINED_).toString()).toContain( + "number" + ); + expect((() => typeof _VALUE_?._PROP_).toString()).toContain("object"); + if (_VALUE_._PROP_._DEFINED_ !== 2) require("fail"); + if (_VALUE_?._PROP_?._DEFINED_ !== 2) require("fail"); + if (typeof _VALUE_._PROP_._DEFINED_ !== "number") require("fail"); + if (typeof _VALUE_?._PROP_?._DEFINED_ !== "number") require("fail"); + if (typeof _VALUE_._PROP_ !== "object") require("fail"); + if (typeof _VALUE_?._PROP_ !== "object") require("fail"); }); diff --git a/test/hotCases/parsing/hot-api-optional-chaining/a.js b/test/hotCases/parsing/hot-api-optional-chaining/a.js index 01cd3e7139e..4fd27070716 100644 --- a/test/hotCases/parsing/hot-api-optional-chaining/a.js +++ b/test/hotCases/parsing/hot-api-optional-chaining/a.js @@ -1 +1,3 @@ -module.exports = "a"; \ No newline at end of file +export default 1; +--- +export default 2; diff --git a/test/hotCases/parsing/hot-api-optional-chaining/index.js b/test/hotCases/parsing/hot-api-optional-chaining/index.js index a1f5a1f79ff..6e2ac273a9d 100644 --- a/test/hotCases/parsing/hot-api-optional-chaining/index.js +++ b/test/hotCases/parsing/hot-api-optional-chaining/index.js @@ -1,4 +1,12 @@ -it("should run module.hot.accept(…)", function() { - module?.hot?.accept("./a", function() {}); - module?.hot.accept(); +import value from "./a"; + +it("should run module.hot.accept(…)", function (done) { + expect(value).toBe(1); + module?.hot?.accept("./a", function () {}); + NEXT( + require("../../update")(done, true, () => { + expect(value).toBe(2); + done(); + }) + ); }); From 0bb9c34f1e8c6c64eb0df708169a19a727635640 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Fri, 7 Aug 2020 16:43:13 +0200 Subject: [PATCH 5/5] optional chaining leads to undefined and never null --- lib/ConstPlugin.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/ConstPlugin.js b/lib/ConstPlugin.js index 21fbaf99a69..844da0c6fa3 100644 --- a/lib/ConstPlugin.js +++ b/lib/ConstPlugin.js @@ -420,10 +420,7 @@ class ConstPlugin { // // ------------------------------------------ // - const dep = new ConstDependency( - evaluated.isNull() ? " null" : " undefined", - expr.range - ); + const dep = new ConstDependency(" undefined", expr.range); dep.loc = expr.loc; parser.state.module.addPresentationalDependency(dep); return true;