Skip to content

Commit

Permalink
add guards support in JavascriptParser
Browse files Browse the repository at this point in the history
  • Loading branch information
vankop committed Mar 17, 2022
1 parent 86a8bd9 commit 9925ea2
Show file tree
Hide file tree
Showing 11 changed files with 517 additions and 76 deletions.
36 changes: 36 additions & 0 deletions lib/dependencies/HarmonyExportPresenceImportSpecifierDependency.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
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");
const NullDependency = require("./NullDependency");

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

/**
* Dependency just for export presence import specifier.
*/
class HarmonyExportPresenceImportSpecifierDependency extends HarmonyImportSpecifierDependency {
get type() {
return "export presence harmony import specifier";
}
}

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

HarmonyExportPresenceImportSpecifierDependency.Template =
/** @type {any} can't cast to HarmonyImportSpecifierDependency.Template */ (
NullDependency.Template
);

module.exports = HarmonyExportPresenceImportSpecifierDependency;
185 changes: 152 additions & 33 deletions lib/dependencies/HarmonyImportDependencyParserPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const ConstDependency = require("./ConstDependency");
const HarmonyAcceptDependency = require("./HarmonyAcceptDependency");
const HarmonyAcceptImportDependency = require("./HarmonyAcceptImportDependency");
const HarmonyEvaluatedImportSpecifierDependency = require("./HarmonyEvaluatedImportSpecifierDependency");
const HarmonyExportPresenceImportSpecifierDependency = require("./HarmonyExportPresenceImportSpecifierDependency");
const HarmonyExports = require("./HarmonyExports");
const { ExportPresenceModes } = require("./HarmonyImportDependency");
const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency");
Expand Down Expand Up @@ -96,6 +97,34 @@ module.exports = class HarmonyImportDependencyParserPlugin {
return node;
}

/**
* @param {string[]} ids ids
* @returns {string} guard name
*/
const createGuard = ids => ids.join(".");
/**
* @param {string} guard guard
* @param {number} idsLength ids length
* @returns {number} mode
*/
const detectExportPresenceMode = (guard, idsLength) => {
if (
exportPresenceMode === ExportPresenceModes.NONE ||
// namespace objects are safe to use
idsLength === 0 ||
parser.scope.guards.has(guard) ||
// if possible guard is in guard position,
// it is guarded by member chain minus one element. e.g.
// if (a && a.b) {} or if (a && "c" in a.b) {}
// a.b is guarded by a
(parser.scope.inGuardPosition &&
(idsLength === 1 ||
parser.scope.guards.has(guard.slice(0, guard.lastIndexOf(".")))))
)
return ExportPresenceModes.NONE;

return exportPresenceMode;
};
parser.hooks.isPure
.for("Identifier")
.tap("HarmonyImportDependencyParserPlugin", expression => {
Expand Down Expand Up @@ -147,53 +176,137 @@ module.exports = class HarmonyImportDependencyParserPlugin {
parser.hooks.binaryExpression.tap(
"HarmonyImportDependencyParserPlugin",
expression => {
if (expression.operator !== "in") return;
if (expression.operator === "in") {
const leftPartEvaluated = parser.evaluateExpression(expression.left);
if (!leftPartEvaluated || leftPartEvaluated.couldHaveSideEffects())
return;
const leftPart = leftPartEvaluated.asString();
if (!leftPart) 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 || !rightPart.isIdentifier()) 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 baseIds = settings.ids.concat(members);
const ids = baseIds.concat([leftPart]);
const dep = new HarmonyEvaluatedImportSpecifierDependency(
settings.source,
settings.sourceOrder,
ids,
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));

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;
if (parser.scope.inGuardPosition) {
parser.scope.guards.add(createGuard(ids));
// namespace objects and identifiers are safe to use
if (baseIds.length > 1) {
// check for export presence for right side expression
const mode = detectExportPresenceMode(
createGuard(baseIds),
baseIds.length
);
// e.g if ("a" in b.c) {}
// here "b.c" is not guarded by "b"
if (mode !== ExportPresenceModes.NONE) {
const dep = new HarmonyExportPresenceImportSpecifierDependency(
settings.source,
settings.sourceOrder,
baseIds,
settings.name,
rightPart.expression.range,
mode,
settings.assertions
);
dep.loc = rightPart.expression.loc;
parser.state.module.addDependency(dep);
}
}
}

return true;
} else if (expression.operator === "!=") {
if (!parser.scope.inGuardPosition) return;
let identifierEvaluated;
const leftPartEvaluated = parser.evaluateExpression(expression.left);
if (!leftPartEvaluated) {
return;
} else if (leftPartEvaluated.isIdentifier()) {
const rootInfo = leftPartEvaluated.rootInfo;
if (
!rootInfo ||
!rootInfo.tagInfo ||
rootInfo.tagInfo.tag !== harmonySpecifierTag
)
return;
identifierEvaluated = leftPartEvaluated;
} else if (leftPartEvaluated.isFalsy() !== true) {
return;
}

const rightPartEvaluated = parser.evaluateExpression(
expression.right
);
if (!rightPartEvaluated) {
return;
} else if (
!identifierEvaluated &&
rightPartEvaluated.isIdentifier()
) {
const rootInfo = rightPartEvaluated.rootInfo;
if (
!rootInfo ||
!rootInfo.tagInfo ||
rootInfo.tagInfo.tag !== harmonySpecifierTag
)
return;
identifierEvaluated = rightPartEvaluated;
} else if (rightPartEvaluated.isFalsy() !== true) {
return;
}

// other hooks will add guards and dependencies
parser.walkExpression(identifierEvaluated.expression);
return true;
}
}
);
parser.hooks.expression
.for(harmonySpecifierTag)
.tap("HarmonyImportDependencyParserPlugin", expr => {
const settings = /** @type {HarmonySettings} */ (parser.currentTagData);
let exportPresenceModeComputed;
// namespace object is safe to use
if (settings.ids.length) {
const guard = createGuard(settings.ids);
exportPresenceModeComputed = detectExportPresenceMode(
guard,
settings.ids.length
);
if (parser.scope.inGuardPosition) parser.scope.guards.add(guard);
}
const dep = new HarmonyImportSpecifierDependency(
settings.source,
settings.sourceOrder,
settings.ids,
settings.name,
expr.range,
exportPresenceMode,
exportPresenceModeComputed || ExportPresenceModes.NONE,
settings.assertions
);
dep.shorthand = parser.scope.inShorthand;
Expand Down Expand Up @@ -224,6 +337,12 @@ module.exports = class HarmonyImportDependencyParserPlugin {
)
: expression;
const ids = settings.ids.concat(nonOptionalMembers);
const guard = createGuard(ids);
const exportPresenceMode = detectExportPresenceMode(
guard,
ids.length
);
if (parser.scope.inGuardPosition) parser.scope.guards.add(guard);
const dep = new HarmonyImportSpecifierDependency(
settings.source,
settings.sourceOrder,
Expand Down Expand Up @@ -267,7 +386,7 @@ module.exports = class HarmonyImportDependencyParserPlugin {
ids,
settings.name,
expr.range,
exportPresenceMode,
detectExportPresenceMode(createGuard(ids), ids.length),
settings.assertions
);
dep.directImport = members.length === 0;
Expand Down
10 changes: 10 additions & 0 deletions lib/dependencies/HarmonyModulesPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const HarmonyEvaluatedImportSpecifierDependency = require("./HarmonyEvaluatedImp
const HarmonyExportExpressionDependency = require("./HarmonyExportExpressionDependency");
const HarmonyExportHeaderDependency = require("./HarmonyExportHeaderDependency");
const HarmonyExportImportedSpecifierDependency = require("./HarmonyExportImportedSpecifierDependency");
const HarmonyExportPresenceImportSpecifierDependency = require("./HarmonyExportPresenceImportSpecifierDependency");
const HarmonyExportSpecifierDependency = require("./HarmonyExportSpecifierDependency");
const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency");
const HarmonyImportSpecifierDependency = require("./HarmonyImportSpecifierDependency");
Expand Down Expand Up @@ -69,6 +70,15 @@ class HarmonyModulesPlugin {
new HarmonyEvaluatedImportSpecifierDependency.Template()
);

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

compilation.dependencyTemplates.set(
HarmonyExportHeaderDependency,
new HarmonyExportHeaderDependency.Template()
Expand Down
11 changes: 11 additions & 0 deletions lib/javascript/BasicEvaluatedExpression.js
Original file line number Diff line number Diff line change
Expand Up @@ -301,46 +301,57 @@ class BasicEvaluatedExpression {
this.type = TypeString;
this.string = string;
this.sideEffects = false;
if (string === "") this.falsy = true;
else this.truthy = true;
return this;
}

setUndefined() {
this.type = TypeUndefined;
this.sideEffects = false;
this.falsy = true;
return this;
}

setNull() {
this.type = TypeNull;
this.sideEffects = false;
this.falsy = true;
return this;
}

setNumber(number) {
this.type = TypeNumber;
this.number = number;
this.sideEffects = false;
if (number === 0) this.falsy = true;
else this.truthy = true;
return this;
}

setBigInt(bigint) {
this.type = TypeBigInt;
this.bigint = bigint;
this.sideEffects = false;
if (bigint === BigInt(0)) this.falsy = true;
else this.truthy = true;
return this;
}

setBoolean(bool) {
this.type = TypeBoolean;
this.bool = bool;
this.sideEffects = false;
if (bool === false) this.falsy = true;
else this.truthy = true;
return this;
}

setRegExp(regExp) {
this.type = TypeRegExp;
this.regExp = regExp;
this.sideEffects = false;
this.truthy = true;
return this;
}

Expand Down

0 comments on commit 9925ea2

Please sign in to comment.