Skip to content

Commit

Permalink
Merge pull request #17203 from bworline/ns
Browse files Browse the repository at this point in the history
Normalize property accessors for es6 namespaces and chained member/call expressions
  • Loading branch information
alexander-akait committed May 31, 2023
2 parents 2a669ff + 77c4deb commit 53c98f0
Show file tree
Hide file tree
Showing 20 changed files with 342 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class HarmonyEvaluatedImportSpecifierDependency extends HarmonyImportSpecifierDe
* @param {string} operator operator
*/
constructor(request, sourceOrder, ids, name, range, assertions, operator) {
super(request, sourceOrder, ids, name, range, false, assertions);
super(request, sourceOrder, ids, name, range, false, assertions, []);
this.operator = operator;
}

Expand Down
23 changes: 18 additions & 5 deletions lib/dependencies/HarmonyImportDependencyParserPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,8 @@ module.exports = class HarmonyImportDependencyParserPlugin {
settings.name,
expr.range,
exportPresenceMode,
settings.assertions
settings.assertions,
[]
);
dep.referencedPropertiesInDestructuring =
parser.destructuringAssignmentPropertiesFor(expr);
Expand All @@ -211,14 +212,19 @@ module.exports = class HarmonyImportDependencyParserPlugin {
.for(harmonySpecifierTag)
.tap(
"HarmonyImportDependencyParserPlugin",
(expression, members, membersOptionals) => {
(expression, members, membersOptionals, memberRangeStarts) => {
const settings = /** @type {HarmonySettings} */ (
parser.currentTagData
);
const nonOptionalMembers = getNonOptionalPart(
members,
membersOptionals
);
const rangeStarts = memberRangeStarts.slice(
0,
memberRangeStarts.length -
(members.length - nonOptionalMembers.length)
);
const expr =
nonOptionalMembers !== members
? getNonOptionalMemberChain(
Expand All @@ -234,7 +240,8 @@ module.exports = class HarmonyImportDependencyParserPlugin {
settings.name,
expr.range,
exportPresenceMode,
settings.assertions
settings.assertions,
rangeStarts
);
dep.referencedPropertiesInDestructuring =
parser.destructuringAssignmentPropertiesFor(expr);
Expand All @@ -249,7 +256,7 @@ module.exports = class HarmonyImportDependencyParserPlugin {
.for(harmonySpecifierTag)
.tap(
"HarmonyImportDependencyParserPlugin",
(expression, members, membersOptionals) => {
(expression, members, membersOptionals, memberRangeStarts) => {
const { arguments: args, callee } = expression;
const settings = /** @type {HarmonySettings} */ (
parser.currentTagData
Expand All @@ -258,6 +265,11 @@ module.exports = class HarmonyImportDependencyParserPlugin {
members,
membersOptionals
);
const rangeStarts = memberRangeStarts.slice(
0,
memberRangeStarts.length -
(members.length - nonOptionalMembers.length)
);
const expr =
nonOptionalMembers !== members
? getNonOptionalMemberChain(
Expand All @@ -273,7 +285,8 @@ module.exports = class HarmonyImportDependencyParserPlugin {
settings.name,
expr.range,
exportPresenceMode,
settings.assertions
settings.assertions,
rangeStarts
);
dep.directImport = members.length === 0;
dep.call = true;
Expand Down
81 changes: 75 additions & 6 deletions lib/dependencies/HarmonyImportSpecifierDependency.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
* @param {Range} range range
* @param {TODO} exportPresenceMode export presence mode
* @param {Assertions=} assertions assertions
* @param {number[]=} idRangeStarts range starts for members of ids; the two arrays are right-aligned
*/
constructor(
request,
Expand All @@ -51,12 +52,14 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
name,
range,
exportPresenceMode,
assertions
assertions,
idRangeStarts // TODO webpack 6 make this non-optional. It must always be set to properly trim ids.
) {
super(request, sourceOrder, assertions);
this.ids = ids;
this.name = name;
this.range = range;
this.idRangeStarts = idRangeStarts;
this.exportPresenceMode = exportPresenceMode;
this.namespaceObjectAsContext = false;
this.call = undefined;
Expand Down Expand Up @@ -258,6 +261,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
write(this.ids);
write(this.name);
write(this.range);
write(this.idRangeStarts);
write(this.exportPresenceMode);
write(this.namespaceObjectAsContext);
write(this.call);
Expand All @@ -277,6 +281,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
this.ids = read();
this.name = read();
this.range = read();
this.idRangeStarts = read();
this.exportPresenceMode = read();
this.namespaceObjectAsContext = read();
this.call = read();
Expand Down Expand Up @@ -310,14 +315,78 @@ HarmonyImportSpecifierDependency.Template = class HarmonyImportSpecifierDependen
// 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;
const ids = dep.getIds(moduleGraph); // determine minimal set of IDs.
let trimmedIds = this._trimIdsToThoseImported(ids, moduleGraph, dep);

let [rangeStart, rangeEnd] = dep.range;
if (trimmedIds.length !== ids.length) {
// The array returned from dep.idRangeStarts is right-aligned with the array returned from dep.getIds.
// Meaning, the two arrays may not always have the same number of elements, but the last element of
// dep.idRangeStarts corresponds to [the starting range position of] the last element of dep.getIds.
// Use this to find the correct range end position based on the number of ids that were trimmed.
const idx =
dep.idRangeStarts === undefined
? -1 /* trigger failure case below */
: dep.idRangeStarts.length + (trimmedIds.length - ids.length);
if (idx < 0 || idx >= dep.idRangeStarts.length) {
// cspell:ignore minifiers
// Should not happen but we can't throw an error here because of backward compatibility with
// external plugins in wp5. Instead, we just disable trimming for now. This may break some minifiers.
trimmedIds = ids;
// TODO webpack 6 remove the "trimmedIds = ids" above and uncomment the following line instead.
// throw new Error("Missing range starts data for id replacement trimming.");
} else {
rangeEnd = dep.idRangeStarts[idx];
}
}

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

/**
* @summary Determine which IDs in the id chain are actually referring to namespaces or imports,
* and which are deeper member accessors on the imported object. Only the former should be re-rendered.
* @param {string[]} ids ids
* @param {ModuleGraph} moduleGraph moduleGraph
* @param {HarmonyImportSpecifierDependency} dependency dependency
* @returns {string[]} generated code
*/
_trimIdsToThoseImported(ids, moduleGraph, dependency) {
let trimmedIds = [];
const exportsInfo = moduleGraph.getExportsInfo(
moduleGraph.getModule(dependency)
);
let currentExportsInfo = /** @type {ExportsInfo=} */ exportsInfo;
for (let i = 0; i < ids.length; i++) {
if (i === 0 && ids[i] === "default") {
continue; // ExportInfo for the next level under default is still at the root ExportsInfo, so don't advance currentExportsInfo
}
const exportInfo = currentExportsInfo.getExportInfo(ids[i]);
if (exportInfo.provided === false) {
// json imports have nested ExportInfo for elements that things that are not actually exported, so check .provided
trimmedIds = ids.slice(0, i);
break;
}
const nestedInfo = exportInfo.getNestedExportsInfo();
if (!nestedInfo) {
// once all nested exports are traversed, the next item is the actual import so stop there
trimmedIds = ids.slice(0, i + 1);
break;
}
currentExportsInfo = nestedInfo;
}
// Never trim to nothing. This can happen for invalid imports (e.g. import { notThere } from "./module", or import { anything } from "./missingModule")
return trimmedIds.length ? trimmedIds : ids;
}

/**
Expand Down
12 changes: 11 additions & 1 deletion lib/javascript/BasicEvaluatedExpression.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ class BasicEvaluatedExpression {
this.getMembers = undefined;
/** @type {() => boolean[]} */
this.getMembersOptionals = undefined;
/** @type {() => number[]} */
this.getMemberRangeStarts = undefined;
/** @type {EsTreeNode} */
this.expression = undefined;
}
Expand Down Expand Up @@ -384,14 +386,22 @@ class BasicEvaluatedExpression {
* @param {string | VariableInfoInterface} rootInfo root info
* @param {() => string[]} getMembers members
* @param {() => boolean[]=} getMembersOptionals optional members
* @param {() => number[]=} getMemberRangeStarts range start of progressively increasing sub-expressions
* @returns {this} this
*/
setIdentifier(identifier, rootInfo, getMembers, getMembersOptionals) {
setIdentifier(
identifier,
rootInfo,
getMembers,
getMembersOptionals,
getMemberRangeStarts
) {
this.type = TypeIdentifier;
this.identifier = identifier;
this.rootInfo = rootInfo;
this.getMembers = getMembers;
this.getMembersOptionals = getMembersOptionals;
this.getMemberRangeStarts = getMemberRangeStarts;
this.sideEffects = true;
return this;
}
Expand Down

0 comments on commit 53c98f0

Please sign in to comment.