Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

support destructuring assignment in parser #16941

Merged
merged 7 commits into from Apr 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 18 additions & 6 deletions lib/DefinePlugin.js
Expand Up @@ -118,6 +118,7 @@ class RuntimeValue {
* @param {string} key the defined key
* @param {RuntimeTemplate} runtimeTemplate the runtime template
* @param {boolean|undefined|null=} asiSafe asi safe (undefined: unknown, null: unneeded)
* @param {Set<string>|undefined=} objKeys used keys
* @returns {string} code converted to string that evaluates
*/
const stringifyObj = (
Expand All @@ -126,7 +127,8 @@ const stringifyObj = (
valueCacheVersions,
key,
runtimeTemplate,
asiSafe
asiSafe,
objKeys
) => {
let code;
let arr = Array.isArray(obj);
Expand All @@ -137,7 +139,12 @@ const stringifyObj = (
)
.join(",")}]`;
} else {
code = `{${Object.keys(obj)
let keys = Object.keys(obj);
if (objKeys) {
if (objKeys.size === 0) keys = [];
else keys = keys.filter(k => objKeys.has(k));
}
code = `{${keys
.map(key => {
const code = obj[key];
return (
Expand Down Expand Up @@ -169,6 +176,7 @@ const stringifyObj = (
* @param {string} key the defined key
* @param {RuntimeTemplate} runtimeTemplate the runtime template
* @param {boolean|undefined|null=} asiSafe asi safe (undefined: unknown, null: unneeded)
* @param {Set<string>|undefined=} objKeys used keys
* @returns {string} code converted to string that evaluates
*/
const toCode = (
Expand All @@ -177,7 +185,8 @@ const toCode = (
valueCacheVersions,
key,
runtimeTemplate,
asiSafe
asiSafe,
objKeys
) => {
if (code === null) {
return "null";
Expand Down Expand Up @@ -211,7 +220,8 @@ const toCode = (
valueCacheVersions,
key,
runtimeTemplate,
asiSafe
asiSafe,
objKeys
);
}
if (typeof code === "bigint") {
Expand Down Expand Up @@ -426,7 +436,8 @@ class DefinePlugin {
compilation.valueCacheVersions,
originalKey,
runtimeTemplate,
!parser.isAsiPosition(expr.range[0])
!parser.isAsiPosition(expr.range[0]),
parser.destructuringAssignmentPropertiesFor(expr)
);
if (WEBPACK_REQUIRE_FUNCTION_REGEXP.test(strCode)) {
return toConstantDependency(parser, strCode, [
Expand Down Expand Up @@ -523,7 +534,8 @@ class DefinePlugin {
compilation.valueCacheVersions,
key,
runtimeTemplate,
!parser.isAsiPosition(expr.range[0])
!parser.isAsiPosition(expr.range[0]),
parser.destructuringAssignmentPropertiesFor(expr)
);

if (WEBPACK_REQUIRE_FUNCTION_REGEXP.test(strCode)) {
Expand Down
4 changes: 4 additions & 0 deletions lib/dependencies/HarmonyImportDependencyParserPlugin.js
Expand Up @@ -196,6 +196,8 @@ module.exports = class HarmonyImportDependencyParserPlugin {
exportPresenceMode,
settings.assertions
);
dep.referencedPropertiesInDestructuring =
parser.destructuringAssignmentPropertiesFor(expr);
dep.shorthand = parser.scope.inShorthand;
dep.directImport = true;
dep.asiSafe = !parser.isAsiPosition(expr.range[0]);
Expand Down Expand Up @@ -233,6 +235,8 @@ module.exports = class HarmonyImportDependencyParserPlugin {
exportPresenceMode,
settings.assertions
);
dep.referencedPropertiesInDestructuring =
parser.destructuringAssignmentPropertiesFor(expr);
dep.asiSafe = !parser.isAsiPosition(expr.range[0]);
dep.loc = expr.loc;
parser.state.module.addDependency(dep);
Expand Down
31 changes: 28 additions & 3 deletions lib/dependencies/HarmonyImportSpecifierDependency.js
Expand Up @@ -52,6 +52,8 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
this.asiSafe = undefined;
/** @type {Set<string> | boolean} */
this.usedByExports = undefined;
/** @type {Set<string>} */
this.referencedPropertiesInDestructuring = undefined;
}

// TODO webpack 6 remove
Expand Down Expand Up @@ -121,7 +123,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
*/
getReferencedExports(moduleGraph, runtime) {
let ids = this.getIds(moduleGraph);
if (ids.length === 0) return Dependency.EXPORTS_OBJECT_REFERENCED;
if (ids.length === 0) return this._getReferencedExportsInDestructuring();
let namespaceObjectAsContext = this.namespaceObjectAsContext;
if (ids[0] === "default") {
const selfModule = moduleGraph.getParentModule(this);
Expand All @@ -134,7 +136,8 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
) {
case "default-only":
case "default-with-named":
if (ids.length === 1) return Dependency.EXPORTS_OBJECT_REFERENCED;
if (ids.length === 1)
return this._getReferencedExportsInDestructuring();
ids = ids.slice(1);
namespaceObjectAsContext = true;
break;
Expand All @@ -152,7 +155,27 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
ids = ids.slice(0, -1);
}

return [ids];
return this._getReferencedExportsInDestructuring(ids);
}

/**
* @param {string[]=} ids ids
* @returns {(string[] | ReferencedExport)[]} referenced exports
*/
_getReferencedExportsInDestructuring(ids) {
if (this.referencedPropertiesInDestructuring) {
/** @type {ReferencedExport[]} */
const refs = [];
for (const key of this.referencedPropertiesInDestructuring) {
refs.push({
name: ids ? ids.concat([key]) : [key],
canMangle: false
});
}
return refs;
} else {
return ids ? [ids] : Dependency.EXPORTS_OBJECT_REFERENCED;
}
}

/**
Expand Down Expand Up @@ -226,6 +249,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
write(this.shorthand);
write(this.asiSafe);
write(this.usedByExports);
write(this.referencedPropertiesInDestructuring);
super.serialize(context);
}

Expand All @@ -241,6 +265,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
this.shorthand = read();
this.asiSafe = read();
this.usedByExports = read();
this.referencedPropertiesInDestructuring = read();
super.deserialize(context);
}
}
Expand Down
88 changes: 88 additions & 0 deletions lib/javascript/JavascriptParser.js
Expand Up @@ -331,6 +331,8 @@ class JavascriptParser extends Parser {
/** @type {(StatementNode|ExpressionNode)[]} */
this.statementPath = undefined;
this.prevStatement = undefined;
/** @type {WeakMap<ExpressionNode, Set<string>>} */
this.destructuringAssignmentProperties = undefined;
this.currentTagData = undefined;
this._initializeEvaluating();
}
Expand Down Expand Up @@ -1411,6 +1413,15 @@ class JavascriptParser extends Parser {
});
}

/**
* @param {ExpressionNode} node node
* @returns {Set<string>|undefined} destructured identifiers
*/
destructuringAssignmentPropertiesFor(node) {
if (!this.destructuringAssignmentProperties) return undefined;
return this.destructuringAssignmentProperties.get(node);
}

getRenameIdentifier(expr) {
const result = this.evaluateExpression(expr);
if (result.isIdentifier()) {
Expand Down Expand Up @@ -1557,6 +1568,8 @@ class JavascriptParser extends Parser {
case "ClassDeclaration":
this.blockPreWalkClassDeclaration(statement);
break;
case "ExpressionStatement":
this.blockPreWalkExpressionStatement(statement);
}
this.prevStatement = this.statementPath.pop();
}
Expand Down Expand Up @@ -1890,6 +1903,37 @@ class JavascriptParser extends Parser {
this.scope.topLevelScope = wasTopLevel;
}

blockPreWalkExpressionStatement(statement) {
const expression = statement.expression;
switch (expression.type) {
case "AssignmentExpression":
this.preWalkAssignmentExpression(expression);
}
}

preWalkAssignmentExpression(expression) {
if (
expression.left.type !== "ObjectPattern" ||
!this.destructuringAssignmentProperties
)
return;
const keys = this._preWalkObjectPattern(expression.left);
if (!keys) return;

// check multiple assignments
if (this.destructuringAssignmentProperties.has(expression)) {
const set = this.destructuringAssignmentProperties.get(expression);
this.destructuringAssignmentProperties.delete(expression);
for (const id of set) keys.add(id);
}

this.destructuringAssignmentProperties.set(expression.right, keys);

if (expression.right.type === "AssignmentExpression") {
this.preWalkAssignmentExpression(expression.right);
}
}

blockPreWalkImportDeclaration(statement) {
const source = statement.source.value;
this.hooks.import.call(statement, source);
Expand Down Expand Up @@ -2087,6 +2131,7 @@ class JavascriptParser extends Parser {
for (const declarator of statement.declarations) {
switch (declarator.type) {
case "VariableDeclarator": {
this.preWalkVariableDeclarator(declarator);
if (!this.hooks.preDeclarator.call(declarator, statement)) {
this.enterPattern(declarator.id, (name, decl) => {
let hook = hookMap.get(name);
Expand All @@ -2104,6 +2149,47 @@ class JavascriptParser extends Parser {
}
}

_preWalkObjectPattern(objectPattern) {
const ids = new Set();
const properties = objectPattern.properties;
for (let i = 0; i < properties.length; i++) {
const property = properties[i];
if (property.type !== "Property") return;
const key = property.key;
if (key.type === "Identifier") {
ids.add(key.name);
} else {
const id = this.evaluateExpression(key);
const str = id.asString();
if (str) {
ids.add(str);
} else {
// could not evaluate key
return;
}
}
}

return ids;
}

preWalkVariableDeclarator(declarator) {
if (
!declarator.init ||
declarator.id.type !== "ObjectPattern" ||
!this.destructuringAssignmentProperties
)
return;
const keys = this._preWalkObjectPattern(declarator.id);

if (!keys) return;
this.destructuringAssignmentProperties.set(declarator.init, keys);

if (declarator.init.type === "AssignmentExpression") {
this.preWalkAssignmentExpression(declarator.init);
}
}

walkVariableDeclaration(statement) {
for (const declarator of statement.declarations) {
switch (declarator.type) {
Expand Down Expand Up @@ -3367,12 +3453,14 @@ class JavascriptParser extends Parser {
this.statementPath = [];
this.prevStatement = undefined;
if (this.hooks.program.call(ast, comments) === undefined) {
this.destructuringAssignmentProperties = new WeakMap();
this.detectMode(ast.body);
this.preWalkStatements(ast.body);
this.prevStatement = undefined;
this.blockPreWalkStatements(ast.body);
this.prevStatement = undefined;
this.walkStatements(ast.body);
this.destructuringAssignmentProperties = undefined;
}
this.hooks.finish.call(ast, comments);
this.scope = oldScope;
Expand Down
@@ -0,0 +1,9 @@
export let counter = 0;
export const d = 1;
export const c = 1;

export const exportsInfo = {
counter: __webpack_exports_info__.counter.used,
d: __webpack_exports_info__.d.used,
c: __webpack_exports_info__.c.used
};
@@ -0,0 +1,7 @@
export let counter = 0;
export const d = 1;

export const exportsInfo = {
counter: __webpack_exports_info__.counter.used,
d: __webpack_exports_info__.d.used
};
@@ -0,0 +1,7 @@
export let counter = 0;
export const d = 1;

export const exportsInfo = {
counter: __webpack_exports_info__.counter.used,
d: __webpack_exports_info__.d.used
};
15 changes: 15 additions & 0 deletions test/cases/parsing/harmony-destructuring-assignment/counter4.js
@@ -0,0 +1,15 @@
export let counter = 0;
export const d = 1;
export const c = 1;
export const e = 1;
export const f = 1;
export const g = 1;

export const exportsInfo = {
counter: __webpack_exports_info__.counter.used,
d: __webpack_exports_info__.d.used,
c: __webpack_exports_info__.c.used,
e: __webpack_exports_info__.e.used,
f: __webpack_exports_info__.f.used,
g: __webpack_exports_info__.g.used
};