Skip to content

Commit 86a8bd9

Browse files
authored
Merge pull request #15455 from webpack/feature/support-in-operator
add "in" operator support
2 parents aca885c + cb05e41 commit 86a8bd9

File tree

17 files changed

+230
-14
lines changed

17 files changed

+230
-14
lines changed
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
MIT License http://www.opensource.org/licenses/mit-license.php
3+
Author Ivan Kopeykin @vankop
4+
*/
5+
6+
"use strict";
7+
8+
const makeSerializable = require("../util/makeSerializable");
9+
const HarmonyImportSpecifierDependency = require("./HarmonyImportSpecifierDependency");
10+
11+
/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
12+
/** @typedef {import("../ChunkGraph")} ChunkGraph */
13+
/** @typedef {import("../Dependency")} Dependency */
14+
/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
15+
16+
/**
17+
* Dependency for static evaluating import specifier. e.g.
18+
* @example
19+
* import a from "a";
20+
* "x" in a;
21+
* a.x !== undefined; // if x value statically analyzable
22+
*/
23+
class HarmonyEvaluatedImportSpecifierDependency extends HarmonyImportSpecifierDependency {
24+
constructor(request, sourceOrder, ids, name, range, assertions, operator) {
25+
super(request, sourceOrder, ids, name, range, false, assertions);
26+
this.operator = operator;
27+
}
28+
29+
get type() {
30+
return `evaluated X ${this.operator} harmony import specifier`;
31+
}
32+
33+
serialize(context) {
34+
super.serialize(context);
35+
const { write } = context;
36+
write(this.operator);
37+
}
38+
39+
deserialize(context) {
40+
super.deserialize(context);
41+
const { read } = context;
42+
this.operator = read();
43+
}
44+
}
45+
46+
makeSerializable(
47+
HarmonyEvaluatedImportSpecifierDependency,
48+
"webpack/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency"
49+
);
50+
51+
HarmonyEvaluatedImportSpecifierDependency.Template = class HarmonyEvaluatedImportSpecifierDependencyTemplate extends (
52+
HarmonyImportSpecifierDependency.Template
53+
) {
54+
/**
55+
* @param {Dependency} dependency the dependency for which the template should be applied
56+
* @param {ReplaceSource} source the current replace source which can be modified
57+
* @param {DependencyTemplateContext} templateContext the context object
58+
* @returns {void}
59+
*/
60+
apply(dependency, source, templateContext) {
61+
const dep = /** @type {HarmonyEvaluatedImportSpecifierDependency} */ (
62+
dependency
63+
);
64+
const { moduleGraph, runtime } = templateContext;
65+
const connection = moduleGraph.getConnection(dep);
66+
// Skip rendering depending when dependency is conditional
67+
if (connection && !connection.isTargetActive(runtime)) return;
68+
69+
const exportsInfo = moduleGraph.getExportsInfo(connection.module);
70+
const ids = dep.getIds(moduleGraph);
71+
const value = exportsInfo.isExportProvided(ids);
72+
73+
if (typeof value === "boolean") {
74+
source.replace(dep.range[0], dep.range[1] - 1, `${value}`);
75+
} else {
76+
const usedName = exportsInfo.getUsedName(ids, runtime);
77+
78+
const code = this._getCodeForIds(
79+
dep,
80+
source,
81+
templateContext,
82+
ids.slice(0, -1)
83+
);
84+
source.replace(
85+
dep.range[0],
86+
dep.range[1] - 1,
87+
`${
88+
usedName ? JSON.stringify(usedName[usedName.length - 1]) : '""'
89+
} in ${code}`
90+
);
91+
}
92+
}
93+
};
94+
95+
module.exports = HarmonyEvaluatedImportSpecifierDependency;

lib/dependencies/HarmonyImportDependencyParserPlugin.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const InnerGraph = require("../optimize/InnerGraph");
1010
const ConstDependency = require("./ConstDependency");
1111
const HarmonyAcceptDependency = require("./HarmonyAcceptDependency");
1212
const HarmonyAcceptImportDependency = require("./HarmonyAcceptImportDependency");
13+
const HarmonyEvaluatedImportSpecifierDependency = require("./HarmonyEvaluatedImportSpecifierDependency");
1314
const HarmonyExports = require("./HarmonyExports");
1415
const { ExportPresenceModes } = require("./HarmonyImportDependency");
1516
const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency");
@@ -143,6 +144,45 @@ module.exports = class HarmonyImportDependencyParserPlugin {
143144
return true;
144145
}
145146
);
147+
parser.hooks.binaryExpression.tap(
148+
"HarmonyImportDependencyParserPlugin",
149+
expression => {
150+
if (expression.operator !== "in") return;
151+
152+
const leftPartEvaluated = parser.evaluateExpression(expression.left);
153+
if (leftPartEvaluated.couldHaveSideEffects()) return;
154+
const leftPart = leftPartEvaluated.asString();
155+
if (!leftPart) return;
156+
157+
const rightPart = parser.evaluateExpression(expression.right);
158+
if (!rightPart.isIdentifier()) return;
159+
160+
const rootInfo = rightPart.rootInfo;
161+
if (
162+
!rootInfo ||
163+
!rootInfo.tagInfo ||
164+
rootInfo.tagInfo.tag !== harmonySpecifierTag
165+
)
166+
return;
167+
const settings = rootInfo.tagInfo.data;
168+
const members = rightPart.getMembers();
169+
const dep = new HarmonyEvaluatedImportSpecifierDependency(
170+
settings.source,
171+
settings.sourceOrder,
172+
settings.ids.concat(members).concat([leftPart]),
173+
settings.name,
174+
expression.range,
175+
settings.assertions,
176+
"in"
177+
);
178+
dep.directImport = members.length === 0;
179+
dep.asiSafe = !parser.isAsiPosition(expression.range[0]);
180+
dep.loc = expression.loc;
181+
parser.state.module.addDependency(dep);
182+
InnerGraph.onUsage(parser.state, e => (dep.usedByExports = e));
183+
return true;
184+
}
185+
);
146186
parser.hooks.expression
147187
.for(harmonySpecifierTag)
148188
.tap("HarmonyImportDependencyParserPlugin", expr => {

lib/dependencies/HarmonyImportSpecifierDependency.js

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -261,14 +261,32 @@ HarmonyImportSpecifierDependency.Template = class HarmonyImportSpecifierDependen
261261
*/
262262
apply(dependency, source, templateContext) {
263263
const dep = /** @type {HarmonyImportSpecifierDependency} */ (dependency);
264-
const { moduleGraph, module, runtime, concatenationScope } =
265-
templateContext;
264+
const { moduleGraph, runtime } = templateContext;
266265
const connection = moduleGraph.getConnection(dep);
267266
// Skip rendering depending when dependency is conditional
268267
if (connection && !connection.isTargetActive(runtime)) return;
269268

270269
const ids = dep.getIds(moduleGraph);
270+
const exportExpr = this._getCodeForIds(dep, source, templateContext, ids);
271+
const range = dep.range;
272+
if (dep.shorthand) {
273+
source.insert(range[1], `: ${exportExpr}`);
274+
} else {
275+
source.replace(range[0], range[1] - 1, exportExpr);
276+
}
277+
}
271278

279+
/**
280+
* @param {HarmonyImportSpecifierDependency} dep dependency
281+
* @param {ReplaceSource} source source
282+
* @param {DependencyTemplateContext} templateContext context
283+
* @param {string[]} ids ids
284+
* @returns {string} generated code
285+
*/
286+
_getCodeForIds(dep, source, templateContext, ids) {
287+
const { moduleGraph, module, runtime, concatenationScope } =
288+
templateContext;
289+
const connection = moduleGraph.getConnection(dep);
272290
let exportExpr;
273291
if (
274292
connection &&
@@ -299,7 +317,7 @@ HarmonyImportSpecifierDependency.Template = class HarmonyImportSpecifierDependen
299317
);
300318
}
301319
} else {
302-
super.apply(dependency, source, templateContext);
320+
super.apply(dep, source, templateContext);
303321

304322
const { runtimeTemplate, initFragments, runtimeRequirements } =
305323
templateContext;
@@ -320,11 +338,7 @@ HarmonyImportSpecifierDependency.Template = class HarmonyImportSpecifierDependen
320338
runtimeRequirements
321339
});
322340
}
323-
if (dep.shorthand) {
324-
source.insert(dep.range[1], `: ${exportExpr}`);
325-
} else {
326-
source.replace(dep.range[0], dep.range[1] - 1, exportExpr);
327-
}
341+
return exportExpr;
328342
}
329343
};
330344

lib/dependencies/HarmonyModulesPlugin.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
const HarmonyAcceptDependency = require("./HarmonyAcceptDependency");
99
const HarmonyAcceptImportDependency = require("./HarmonyAcceptImportDependency");
1010
const HarmonyCompatibilityDependency = require("./HarmonyCompatibilityDependency");
11+
const HarmonyEvaluatedImportSpecifierDependency = require("./HarmonyEvaluatedImportSpecifierDependency");
1112
const HarmonyExportExpressionDependency = require("./HarmonyExportExpressionDependency");
1213
const HarmonyExportHeaderDependency = require("./HarmonyExportHeaderDependency");
1314
const HarmonyExportImportedSpecifierDependency = require("./HarmonyExportImportedSpecifierDependency");
@@ -59,6 +60,15 @@ class HarmonyModulesPlugin {
5960
new HarmonyImportSpecifierDependency.Template()
6061
);
6162

63+
compilation.dependencyFactories.set(
64+
HarmonyEvaluatedImportSpecifierDependency,
65+
normalModuleFactory
66+
);
67+
compilation.dependencyTemplates.set(
68+
HarmonyEvaluatedImportSpecifierDependency,
69+
new HarmonyEvaluatedImportSpecifierDependency.Template()
70+
);
71+
6272
compilation.dependencyTemplates.set(
6373
HarmonyExportHeaderDependency,
6474
new HarmonyExportHeaderDependency.Template()

lib/javascript/BasicEvaluatedExpression.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ class BasicEvaluatedExpression {
6161
/** @type {BasicEvaluatedExpression | undefined} */
6262
this.postfix = undefined;
6363
this.wrappedInnerExpressions = undefined;
64-
/** @type {string | undefined} */
64+
/** @type {string | VariableInfoInterface | undefined} */
6565
this.identifier = undefined;
6666
/** @type {VariableInfoInterface} */
6767
this.rootInfo = undefined;

lib/javascript/JavascriptParser.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,8 @@ class JavascriptParser extends Parser {
292292
optionalChaining: new SyncBailHook(["optionalChaining"]),
293293
/** @type {HookMap<SyncBailHook<[NewExpressionNode], boolean | void>>} */
294294
new: new HookMap(() => new SyncBailHook(["expression"])),
295+
/** @type {SyncBailHook<[BinaryExpressionNode], boolean | void>} */
296+
binaryExpression: new SyncBailHook(["binaryExpression"]),
295297
/** @type {HookMap<SyncBailHook<[ExpressionNode], boolean | void>>} */
296298
expression: new HookMap(() => new SyncBailHook(["expression"])),
297299
/** @type {HookMap<SyncBailHook<[ExpressionNode, string[], boolean[]], boolean | void>>} */
@@ -2461,7 +2463,9 @@ class JavascriptParser extends Parser {
24612463
}
24622464

24632465
walkBinaryExpression(expression) {
2464-
this.walkLeftRightExpression(expression);
2466+
if (this.hooks.binaryExpression.call(expression) === undefined) {
2467+
this.walkLeftRightExpression(expression);
2468+
}
24652469
}
24662470

24672471
walkLogicalExpression(expression) {
@@ -2496,7 +2500,9 @@ class JavascriptParser extends Parser {
24962500
) {
24972501
this.setVariable(
24982502
expression.left.name,
2499-
this.getVariableInfo(renameIdentifier)
2503+
typeof renameIdentifier === "string"
2504+
? this.getVariableInfo(renameIdentifier)
2505+
: renameIdentifier
25002506
);
25012507
}
25022508
return;
@@ -2631,7 +2637,9 @@ class JavascriptParser extends Parser {
26312637
argOrThis
26322638
)
26332639
) {
2634-
return this.getVariableInfo(renameIdentifier);
2640+
return typeof renameIdentifier === "string"
2641+
? this.getVariableInfo(renameIdentifier)
2642+
: renameIdentifier;
26352643
}
26362644
}
26372645
}

lib/util/internalSerializables.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ module.exports = {
103103
require("../dependencies/HarmonyImportSideEffectDependency"),
104104
"dependencies/HarmonyImportSpecifierDependency": () =>
105105
require("../dependencies/HarmonyImportSpecifierDependency"),
106+
"dependencies/HarmonyEvaluatedImportSpecifierDependency": () =>
107+
require("../dependencies/HarmonyEvaluatedImportSpecifierDependency"),
106108
"dependencies/ImportContextDependency": () =>
107109
require("../dependencies/ImportContextDependency"),
108110
"dependencies/ImportDependency": () =>

test/cases/parsing/harmony-export-import-specifier/index.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ import { d2, usedD1, usedD2 } from "./d.js";
55
import { b1, usedB1, usedB2, usedB3, usedB4 } from "./b.js";
66
import { usedE1, usedE2 } from "./e.js";
77
import { h } from "./h.js";
8+
import * as m from "./m";
9+
import * as o from "./o";
10+
import * as p from "./p";
11+
import * as q from "./q";
12+
import * as so from "./side-effect-free/o";
13+
import * as sm from "./side-effect-free/m";
814

915
it("namespace export as from commonjs should override named export", function () {
1016
expect(x).toBe(1);
@@ -35,3 +41,24 @@ it("complex case should work correctly", () => {
3541
expect(usedE2).toBe(false);
3642
}
3743
});
44+
45+
it("should handle 'm in n' case", () => {
46+
const obj = { aaa: "aaa" in m };
47+
expect(obj.aaa).toBe(true);
48+
expect("aaa" in o).toBe(true);
49+
expect("aaa" in p).toBe(false);
50+
expect("ccc" in m).toBe(false);
51+
expect("aaa" in q).toBe(true);
52+
expect("aaa" in so).toBe(true);
53+
expect("ccc" in sm).toBe(false);
54+
expect("ccc" in (false ? {} : m.ddd)).toBe(true);
55+
expect("ccc" in (false ? {} : sm.ddd)).toBe(true);
56+
expect("ddd" in m.ddd).toBe(false);
57+
expect("ddd" in sm.ddd).toBe(false);
58+
if (process.env.NODE_ENV === "production") {
59+
expect(m.ddd.usedA).toBe(false);
60+
expect(m.usedB).toBe(false);
61+
expect(m.usedA).toBe(true);
62+
expect(m.canMangleA).toBe(true);
63+
}
64+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const aaa = 1;
2+
export const bbb = 2;
3+
export * as ddd from "./n";
4+
export const usedA = __webpack_exports_info__.aaa.used;
5+
export const canMangleA = __webpack_exports_info__.ccc.canMangle;
6+
export const usedB = __webpack_exports_info__.bbb.used;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const ccc = 3;
2+
export const mmm = () => ({});
3+
export const aaa = 1;
4+
export const usedA = __webpack_exports_info__.a.used;
5+
export const canMangleC = __webpack_exports_info__.c.canMangle;

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy