Skip to content

Commit

Permalink
Merge pull request #15455 from webpack/feature/support-in-operator
Browse files Browse the repository at this point in the history
add "in" operator support
  • Loading branch information
sokra committed Mar 17, 2022
2 parents aca885c + cb05e41 commit 86a8bd9
Show file tree
Hide file tree
Showing 17 changed files with 230 additions and 14 deletions.
95 changes: 95 additions & 0 deletions lib/dependencies/HarmonyEvaluatedImportSpecifierDependency.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Ivan Kopeykin @vankop
*/

"use strict";

const makeSerializable = require("../util/makeSerializable");
const HarmonyImportSpecifierDependency = require("./HarmonyImportSpecifierDependency");

/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
/** @typedef {import("../ChunkGraph")} ChunkGraph */
/** @typedef {import("../Dependency")} Dependency */
/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */

/**
* Dependency for static evaluating import specifier. e.g.
* @example
* import a from "a";
* "x" in a;
* a.x !== undefined; // if x value statically analyzable
*/
class HarmonyEvaluatedImportSpecifierDependency extends HarmonyImportSpecifierDependency {
constructor(request, sourceOrder, ids, name, range, assertions, operator) {
super(request, sourceOrder, ids, name, range, false, assertions);
this.operator = operator;
}

get type() {
return `evaluated X ${this.operator} harmony import specifier`;
}

serialize(context) {
super.serialize(context);
const { write } = context;
write(this.operator);
}

deserialize(context) {
super.deserialize(context);
const { read } = context;
this.operator = read();
}
}

makeSerializable(
HarmonyEvaluatedImportSpecifierDependency,
"webpack/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency"
);

HarmonyEvaluatedImportSpecifierDependency.Template = class HarmonyEvaluatedImportSpecifierDependencyTemplate extends (
HarmonyImportSpecifierDependency.Template
) {
/**
* @param {Dependency} dependency the dependency for which the template should be applied
* @param {ReplaceSource} source the current replace source which can be modified
* @param {DependencyTemplateContext} templateContext the context object
* @returns {void}
*/
apply(dependency, source, templateContext) {
const dep = /** @type {HarmonyEvaluatedImportSpecifierDependency} */ (
dependency
);
const { moduleGraph, runtime } = templateContext;
const connection = moduleGraph.getConnection(dep);
// Skip rendering depending when dependency is conditional
if (connection && !connection.isTargetActive(runtime)) return;

const exportsInfo = moduleGraph.getExportsInfo(connection.module);
const ids = dep.getIds(moduleGraph);
const value = exportsInfo.isExportProvided(ids);

if (typeof value === "boolean") {
source.replace(dep.range[0], dep.range[1] - 1, `${value}`);
} else {
const usedName = exportsInfo.getUsedName(ids, runtime);

const code = this._getCodeForIds(
dep,
source,
templateContext,
ids.slice(0, -1)
);
source.replace(
dep.range[0],
dep.range[1] - 1,
`${
usedName ? JSON.stringify(usedName[usedName.length - 1]) : '""'
} in ${code}`
);
}
}
};

module.exports = HarmonyEvaluatedImportSpecifierDependency;
40 changes: 40 additions & 0 deletions lib/dependencies/HarmonyImportDependencyParserPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const InnerGraph = require("../optimize/InnerGraph");
const ConstDependency = require("./ConstDependency");
const HarmonyAcceptDependency = require("./HarmonyAcceptDependency");
const HarmonyAcceptImportDependency = require("./HarmonyAcceptImportDependency");
const HarmonyEvaluatedImportSpecifierDependency = require("./HarmonyEvaluatedImportSpecifierDependency");
const HarmonyExports = require("./HarmonyExports");
const { ExportPresenceModes } = require("./HarmonyImportDependency");
const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency");
Expand Down Expand Up @@ -143,6 +144,45 @@ module.exports = class HarmonyImportDependencyParserPlugin {
return true;
}
);
parser.hooks.binaryExpression.tap(
"HarmonyImportDependencyParserPlugin",
expression => {
if (expression.operator !== "in") return;

const leftPartEvaluated = parser.evaluateExpression(expression.left);
if (leftPartEvaluated.couldHaveSideEffects()) return;
const leftPart = leftPartEvaluated.asString();
if (!leftPart) return;

const rightPart = parser.evaluateExpression(expression.right);
if (!rightPart.isIdentifier()) return;

const rootInfo = rightPart.rootInfo;
if (
!rootInfo ||
!rootInfo.tagInfo ||
rootInfo.tagInfo.tag !== harmonySpecifierTag
)
return;
const settings = rootInfo.tagInfo.data;
const members = rightPart.getMembers();
const dep = new HarmonyEvaluatedImportSpecifierDependency(
settings.source,
settings.sourceOrder,
settings.ids.concat(members).concat([leftPart]),
settings.name,
expression.range,
settings.assertions,
"in"
);
dep.directImport = members.length === 0;
dep.asiSafe = !parser.isAsiPosition(expression.range[0]);
dep.loc = expression.loc;
parser.state.module.addDependency(dep);
InnerGraph.onUsage(parser.state, e => (dep.usedByExports = e));
return true;
}
);
parser.hooks.expression
.for(harmonySpecifierTag)
.tap("HarmonyImportDependencyParserPlugin", expr => {
Expand Down
30 changes: 22 additions & 8 deletions lib/dependencies/HarmonyImportSpecifierDependency.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,14 +261,32 @@ HarmonyImportSpecifierDependency.Template = class HarmonyImportSpecifierDependen
*/
apply(dependency, source, templateContext) {
const dep = /** @type {HarmonyImportSpecifierDependency} */ (dependency);
const { moduleGraph, module, runtime, concatenationScope } =
templateContext;
const { moduleGraph, runtime } = templateContext;
const connection = moduleGraph.getConnection(dep);
// Skip rendering depending when dependency is conditional
if (connection && !connection.isTargetActive(runtime)) return;

const ids = dep.getIds(moduleGraph);
const exportExpr = this._getCodeForIds(dep, source, templateContext, ids);
const range = dep.range;
if (dep.shorthand) {
source.insert(range[1], `: ${exportExpr}`);
} else {
source.replace(range[0], range[1] - 1, exportExpr);
}
}

/**
* @param {HarmonyImportSpecifierDependency} dep dependency
* @param {ReplaceSource} source source
* @param {DependencyTemplateContext} templateContext context
* @param {string[]} ids ids
* @returns {string} generated code
*/
_getCodeForIds(dep, source, templateContext, ids) {
const { moduleGraph, module, runtime, concatenationScope } =
templateContext;
const connection = moduleGraph.getConnection(dep);
let exportExpr;
if (
connection &&
Expand Down Expand Up @@ -299,7 +317,7 @@ HarmonyImportSpecifierDependency.Template = class HarmonyImportSpecifierDependen
);
}
} else {
super.apply(dependency, source, templateContext);
super.apply(dep, source, templateContext);

const { runtimeTemplate, initFragments, runtimeRequirements } =
templateContext;
Expand All @@ -320,11 +338,7 @@ HarmonyImportSpecifierDependency.Template = class HarmonyImportSpecifierDependen
runtimeRequirements
});
}
if (dep.shorthand) {
source.insert(dep.range[1], `: ${exportExpr}`);
} else {
source.replace(dep.range[0], dep.range[1] - 1, exportExpr);
}
return exportExpr;
}
};

Expand Down
10 changes: 10 additions & 0 deletions lib/dependencies/HarmonyModulesPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
const HarmonyAcceptDependency = require("./HarmonyAcceptDependency");
const HarmonyAcceptImportDependency = require("./HarmonyAcceptImportDependency");
const HarmonyCompatibilityDependency = require("./HarmonyCompatibilityDependency");
const HarmonyEvaluatedImportSpecifierDependency = require("./HarmonyEvaluatedImportSpecifierDependency");
const HarmonyExportExpressionDependency = require("./HarmonyExportExpressionDependency");
const HarmonyExportHeaderDependency = require("./HarmonyExportHeaderDependency");
const HarmonyExportImportedSpecifierDependency = require("./HarmonyExportImportedSpecifierDependency");
Expand Down Expand Up @@ -59,6 +60,15 @@ class HarmonyModulesPlugin {
new HarmonyImportSpecifierDependency.Template()
);

compilation.dependencyFactories.set(
HarmonyEvaluatedImportSpecifierDependency,
normalModuleFactory
);
compilation.dependencyTemplates.set(
HarmonyEvaluatedImportSpecifierDependency,
new HarmonyEvaluatedImportSpecifierDependency.Template()
);

compilation.dependencyTemplates.set(
HarmonyExportHeaderDependency,
new HarmonyExportHeaderDependency.Template()
Expand Down
2 changes: 1 addition & 1 deletion lib/javascript/BasicEvaluatedExpression.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class BasicEvaluatedExpression {
/** @type {BasicEvaluatedExpression | undefined} */
this.postfix = undefined;
this.wrappedInnerExpressions = undefined;
/** @type {string | undefined} */
/** @type {string | VariableInfoInterface | undefined} */
this.identifier = undefined;
/** @type {VariableInfoInterface} */
this.rootInfo = undefined;
Expand Down
14 changes: 11 additions & 3 deletions lib/javascript/JavascriptParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,8 @@ class JavascriptParser extends Parser {
optionalChaining: new SyncBailHook(["optionalChaining"]),
/** @type {HookMap<SyncBailHook<[NewExpressionNode], boolean | void>>} */
new: new HookMap(() => new SyncBailHook(["expression"])),
/** @type {SyncBailHook<[BinaryExpressionNode], boolean | void>} */
binaryExpression: new SyncBailHook(["binaryExpression"]),
/** @type {HookMap<SyncBailHook<[ExpressionNode], boolean | void>>} */
expression: new HookMap(() => new SyncBailHook(["expression"])),
/** @type {HookMap<SyncBailHook<[ExpressionNode, string[], boolean[]], boolean | void>>} */
Expand Down Expand Up @@ -2461,7 +2463,9 @@ class JavascriptParser extends Parser {
}

walkBinaryExpression(expression) {
this.walkLeftRightExpression(expression);
if (this.hooks.binaryExpression.call(expression) === undefined) {
this.walkLeftRightExpression(expression);
}
}

walkLogicalExpression(expression) {
Expand Down Expand Up @@ -2496,7 +2500,9 @@ class JavascriptParser extends Parser {
) {
this.setVariable(
expression.left.name,
this.getVariableInfo(renameIdentifier)
typeof renameIdentifier === "string"
? this.getVariableInfo(renameIdentifier)
: renameIdentifier
);
}
return;
Expand Down Expand Up @@ -2631,7 +2637,9 @@ class JavascriptParser extends Parser {
argOrThis
)
) {
return this.getVariableInfo(renameIdentifier);
return typeof renameIdentifier === "string"
? this.getVariableInfo(renameIdentifier)
: renameIdentifier;
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions lib/util/internalSerializables.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ module.exports = {
require("../dependencies/HarmonyImportSideEffectDependency"),
"dependencies/HarmonyImportSpecifierDependency": () =>
require("../dependencies/HarmonyImportSpecifierDependency"),
"dependencies/HarmonyEvaluatedImportSpecifierDependency": () =>
require("../dependencies/HarmonyEvaluatedImportSpecifierDependency"),
"dependencies/ImportContextDependency": () =>
require("../dependencies/ImportContextDependency"),
"dependencies/ImportDependency": () =>
Expand Down
27 changes: 27 additions & 0 deletions test/cases/parsing/harmony-export-import-specifier/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ import { d2, usedD1, usedD2 } from "./d.js";
import { b1, usedB1, usedB2, usedB3, usedB4 } from "./b.js";
import { usedE1, usedE2 } from "./e.js";
import { h } from "./h.js";
import * as m from "./m";
import * as o from "./o";
import * as p from "./p";
import * as q from "./q";
import * as so from "./side-effect-free/o";
import * as sm from "./side-effect-free/m";

it("namespace export as from commonjs should override named export", function () {
expect(x).toBe(1);
Expand Down Expand Up @@ -35,3 +41,24 @@ it("complex case should work correctly", () => {
expect(usedE2).toBe(false);
}
});

it("should handle 'm in n' case", () => {
const obj = { aaa: "aaa" in m };
expect(obj.aaa).toBe(true);
expect("aaa" in o).toBe(true);
expect("aaa" in p).toBe(false);
expect("ccc" in m).toBe(false);
expect("aaa" in q).toBe(true);
expect("aaa" in so).toBe(true);
expect("ccc" in sm).toBe(false);
expect("ccc" in (false ? {} : m.ddd)).toBe(true);
expect("ccc" in (false ? {} : sm.ddd)).toBe(true);
expect("ddd" in m.ddd).toBe(false);
expect("ddd" in sm.ddd).toBe(false);
if (process.env.NODE_ENV === "production") {
expect(m.ddd.usedA).toBe(false);
expect(m.usedB).toBe(false);
expect(m.usedA).toBe(true);
expect(m.canMangleA).toBe(true);
}
});
6 changes: 6 additions & 0 deletions test/cases/parsing/harmony-export-import-specifier/m.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const aaa = 1;
export const bbb = 2;
export * as ddd from "./n";
export const usedA = __webpack_exports_info__.aaa.used;
export const canMangleA = __webpack_exports_info__.ccc.canMangle;
export const usedB = __webpack_exports_info__.bbb.used;
5 changes: 5 additions & 0 deletions test/cases/parsing/harmony-export-import-specifier/n.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const ccc = 3;
export const mmm = () => ({});
export const aaa = 1;
export const usedA = __webpack_exports_info__.a.used;
export const canMangleC = __webpack_exports_info__.c.canMangle;
2 changes: 2 additions & 0 deletions test/cases/parsing/harmony-export-import-specifier/o.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const exports_ = { aaa: 1, bbb: 2 };
module.exports = exports_;
Empty file.
1 change: 1 addition & 0 deletions test/cases/parsing/harmony-export-import-specifier/q.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./o";
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "../m";
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "../o";
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"sideEffects": false
}
5 changes: 3 additions & 2 deletions types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ declare abstract class BasicEvaluatedExpression {
prefix?: BasicEvaluatedExpression;
postfix?: BasicEvaluatedExpression;
wrappedInnerExpressions: any;
identifier?: string;
identifier?: string | VariableInfoInterface;
rootInfo: VariableInfoInterface;
getMembers: () => string[];
getMembersOptionals: () => boolean[];
Expand Down Expand Up @@ -5122,6 +5122,7 @@ declare class JavascriptParser extends Parser {
>;
optionalChaining: SyncBailHook<[ChainExpression], boolean | void>;
new: HookMap<SyncBailHook<[NewExpression], boolean | void>>;
binaryExpression: SyncBailHook<[BinaryExpression], boolean | void>;
expression: HookMap<SyncBailHook<[Expression], boolean | void>>;
expressionMemberChain: HookMap<
SyncBailHook<[Expression, string[], boolean[]], boolean | void>
Expand Down Expand Up @@ -5192,7 +5193,7 @@ declare class JavascriptParser extends Parser {
)[];
prevStatement: any;
currentTagData: any;
getRenameIdentifier(expr?: any): undefined | string;
getRenameIdentifier(expr?: any): undefined | string | VariableInfoInterface;
walkClass(classy: ClassExpression | ClassDeclaration): void;
preWalkStatements(statements?: any): void;
blockPreWalkStatements(statements?: any): void;
Expand Down

0 comments on commit 86a8bd9

Please sign in to comment.