Skip to content

Commit 5908ede

Browse files
committed
fix(@angular-devkit/build-angular): correctly wrap CommonJS exported enums when optimizing
When optimizing a CommonJS exported enum, the build optimizer enum wrapping pass was previously dropping the `exports` object assignment from the enum wrapper function call expression. This would not occur with application code but is possible with library code that was built with TypeScript and shipped as CommonJS. Assuming the following TypeScript enum: ``` export enum ChangeDetectionStrategy { OnPush = 0, Default = 1, } ``` TypeScript 5.1 will emit an exported enum for CommonJS as follows: ``` exports.ChangeDetectionStrategy = void 0; var ChangeDetectionStrategy; (function (ChangeDetectionStrategy) { ChangeDetectionStrategy[ChangeDetectionStrategy["OnPush"] = 0] = "OnPush"; ChangeDetectionStrategy[ChangeDetectionStrategy["Default"] = 1] = "Default"; })(ChangeDetectionStrategy || (exports.ChangeDetectionStrategy = ChangeDetectionStrategy = {})); ``` The build optimizer would previously transform this into: ``` exports.ChangeDetectionStrategy = void 0; var ChangeDetectionStrategy = /*#__PURE__*/ (() => { ChangeDetectionStrategy = ChangeDetectionStrategy || {}; ChangeDetectionStrategy[(ChangeDetectionStrategy["OnPush"] = 5)] = "OnPush"; ChangeDetectionStrategy[(ChangeDetectionStrategy["Default"] = 8)] = "Default"; return ChangeDetectionStrategy; })(); ``` But this has a defect wherein the `exports` assignment is dropped. To rectify this situation, the build optimizer will now transform the code into: ``` exports.ChangeDetectionStrategy = void 0; var ChangeDetectionStrategy = /*#__PURE__*/ (function (ChangeDetectionStrategy) { ChangeDetectionStrategy[(ChangeDetectionStrategy["OnPush"] = 0)] = "OnPush"; ChangeDetectionStrategy[(ChangeDetectionStrategy["Default"] = 1)] = "Default"; return ChangeDetectionStrategy; })(ChangeDetectionStrategy || (exports.ChangeDetectionStrategy = ChangeDetectionStrategy = {})) ``` This retains the `exports` assignment.
1 parent 953104e commit 5908ede

File tree

2 files changed

+97
-82
lines changed

2 files changed

+97
-82
lines changed

packages/angular_devkit/build_angular/src/tools/babel/plugins/adjust-typescript-enums.ts

Lines changed: 26 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,22 @@ export default function (): PluginObj {
5656
return;
5757
}
5858

59-
const enumCallArgument = nextExpression.node.arguments[0];
60-
if (!types.isLogicalExpression(enumCallArgument, { operator: '||' })) {
59+
const enumCallArgument = nextExpression.get('arguments')[0];
60+
if (!enumCallArgument.isLogicalExpression({ operator: '||' })) {
6161
return;
6262
}
6363

64+
const leftCallArgument = enumCallArgument.get('left');
65+
const rightCallArgument = enumCallArgument.get('right');
66+
6467
// Check if identifiers match var declaration
6568
if (
66-
!types.isIdentifier(enumCallArgument.left) ||
67-
!nextExpression.scope.bindingIdentifierEquals(enumCallArgument.left.name, declarationId)
69+
!leftCallArgument.isIdentifier() ||
70+
!nextExpression.scope.bindingIdentifierEquals(
71+
leftCallArgument.node.name,
72+
declarationId,
73+
) ||
74+
!rightCallArgument.isAssignmentExpression()
6875
) {
6976
return;
7077
}
@@ -74,13 +81,9 @@ export default function (): PluginObj {
7481
return;
7582
}
7683

77-
const enumCalleeParam = enumCallee.node.params[0];
78-
const isEnumCalleeMatching =
79-
types.isIdentifier(enumCalleeParam) && enumCalleeParam.name === declarationId.name;
80-
81-
let enumAssignments: types.ExpressionStatement[] | undefined;
82-
if (isEnumCalleeMatching) {
83-
enumAssignments = [];
84+
const parameterId = enumCallee.get('params')[0];
85+
if (!parameterId.isIdentifier()) {
86+
return;
8487
}
8588

8689
// Check if all enum member values are pure.
@@ -100,56 +103,31 @@ export default function (): PluginObj {
100103
}
101104

102105
hasElements = true;
103-
enumAssignments?.push(enumStatement.node);
104106
}
105107

106108
// If there are no enum elements then there is nothing to wrap
107109
if (!hasElements) {
108110
return;
109111
}
110112

113+
// Update right-side of initializer call argument to remove redundant assignment
114+
if (rightCallArgument.get('left').isIdentifier()) {
115+
rightCallArgument.replaceWith(rightCallArgument.get('right'));
116+
}
117+
118+
// Add a return statement to the enum initializer block
119+
enumCallee
120+
.get('body')
121+
.node.body.push(types.returnStatement(types.cloneNode(parameterId.node)));
122+
111123
// Remove existing enum initializer
112124
const enumInitializer = nextExpression.node;
113125
nextExpression.remove();
114126

115-
// Create IIFE block contents
116-
let blockContents;
117-
if (enumAssignments) {
118-
// Loose mode
119-
blockContents = [
120-
types.expressionStatement(
121-
types.assignmentExpression(
122-
'=',
123-
types.cloneNode(declarationId),
124-
types.logicalExpression(
125-
'||',
126-
types.cloneNode(declarationId),
127-
types.objectExpression([]),
128-
),
129-
),
130-
),
131-
...enumAssignments,
132-
];
133-
} else {
134-
blockContents = [types.expressionStatement(enumInitializer)];
135-
}
136-
137-
// Wrap existing enum initializer in a pure annotated IIFE
138-
const container = types.arrowFunctionExpression(
139-
[],
140-
types.blockStatement([
141-
...blockContents,
142-
types.returnStatement(types.cloneNode(declarationId)),
143-
]),
144-
);
145-
const replacementInitializer = types.callExpression(
146-
types.parenthesizedExpression(container),
147-
[],
148-
);
149-
annotateAsPure(replacementInitializer);
127+
annotateAsPure(enumInitializer);
150128

151129
// Add the wrapped enum initializer directly to the variable declaration
152-
declaration.get('init').replaceWith(replacementInitializer);
130+
declaration.get('init').replaceWith(enumInitializer);
153131
},
154132
},
155133
};

packages/angular_devkit/build_angular/src/tools/babel/plugins/adjust-typescript-enums_spec.ts

Lines changed: 71 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@ function testCase({
3636
};
3737
}
3838

39+
// The majority of these test cases are based off TypeScript emitted enum code for the FW's
40+
// `ChangedDetectionStrategy` enum.
41+
// https://github.com/angular/angular/blob/55d412c5b1b0ba9b03174f7ad9907961fcafa970/packages/core/src/change_detection/constants.ts#L18
42+
// ```
43+
// export enum ChangeDetectionStrategy {
44+
// OnPush = 0,
45+
// Default = 1,
46+
// }
47+
// ```
3948
describe('adjust-typescript-enums Babel plugin', () => {
4049
it(
4150
'wraps unexported TypeScript enums',
@@ -48,12 +57,11 @@ describe('adjust-typescript-enums Babel plugin', () => {
4857
})(ChangeDetectionStrategy || (ChangeDetectionStrategy = {}));
4958
`,
5059
expected: `
51-
var ChangeDetectionStrategy = /*#__PURE__*/ (() => {
52-
ChangeDetectionStrategy = ChangeDetectionStrategy || {};
60+
var ChangeDetectionStrategy = /*#__PURE__*/ (function (ChangeDetectionStrategy) {
5361
ChangeDetectionStrategy[(ChangeDetectionStrategy["OnPush"] = 0)] = "OnPush";
5462
ChangeDetectionStrategy[(ChangeDetectionStrategy["Default"] = 1)] = "Default";
5563
return ChangeDetectionStrategy;
56-
})();
64+
})(ChangeDetectionStrategy || {});
5765
`,
5866
}),
5967
);
@@ -69,12 +77,49 @@ describe('adjust-typescript-enums Babel plugin', () => {
6977
})(ChangeDetectionStrategy || (ChangeDetectionStrategy = {}));
7078
`,
7179
expected: `
72-
export var ChangeDetectionStrategy = /*#__PURE__*/ (() => {
73-
ChangeDetectionStrategy = ChangeDetectionStrategy || {};
80+
export var ChangeDetectionStrategy = /*#__PURE__*/ (function (ChangeDetectionStrategy) {
7481
ChangeDetectionStrategy[(ChangeDetectionStrategy["OnPush"] = 0)] = "OnPush";
7582
ChangeDetectionStrategy[(ChangeDetectionStrategy["Default"] = 1)] = "Default";
7683
return ChangeDetectionStrategy;
77-
})();
84+
})(ChangeDetectionStrategy || {});
85+
`,
86+
}),
87+
);
88+
89+
// Even with recent improvements this case is and was never wrapped. However, it also was not broken
90+
// by the transformation. This test ensures that this older emitted enum form does not break with
91+
// any future changes. Over time this older form will be encountered less and less frequently.
92+
// In the future this test case could be considered for removal.
93+
it(
94+
'does not wrap exported TypeScript enums from CommonJS (<5.1)',
95+
testCase({
96+
input: `
97+
var ChangeDetectionStrategy;
98+
(function (ChangeDetectionStrategy) {
99+
ChangeDetectionStrategy[ChangeDetectionStrategy["OnPush"] = 0] = "OnPush";
100+
ChangeDetectionStrategy[ChangeDetectionStrategy["Default"] = 1] = "Default";
101+
})(ChangeDetectionStrategy = exports.ChangeDetectionStrategy || (exports.ChangeDetectionStrategy = {}));
102+
`,
103+
expected: NO_CHANGE,
104+
}),
105+
);
106+
107+
it(
108+
'wraps exported TypeScript enums from CommonJS (5.1+)',
109+
testCase({
110+
input: `
111+
var ChangeDetectionStrategy;
112+
(function (ChangeDetectionStrategy) {
113+
ChangeDetectionStrategy[ChangeDetectionStrategy["OnPush"] = 0] = "OnPush";
114+
ChangeDetectionStrategy[ChangeDetectionStrategy["Default"] = 1] = "Default";
115+
})(ChangeDetectionStrategy || (exports.ChangeDetectionStrategy = ChangeDetectionStrategy = {}));
116+
`,
117+
expected: `
118+
var ChangeDetectionStrategy = /*#__PURE__*/ (function (ChangeDetectionStrategy) {
119+
ChangeDetectionStrategy[(ChangeDetectionStrategy["OnPush"] = 0)] = "OnPush";
120+
ChangeDetectionStrategy[(ChangeDetectionStrategy["Default"] = 1)] = "Default";
121+
return ChangeDetectionStrategy;
122+
})(ChangeDetectionStrategy || (exports.ChangeDetectionStrategy = ChangeDetectionStrategy = {}));
78123
`,
79124
}),
80125
);
@@ -90,12 +135,11 @@ describe('adjust-typescript-enums Babel plugin', () => {
90135
})(ChangeDetectionStrategy || (ChangeDetectionStrategy = {}));
91136
`,
92137
expected: `
93-
export var ChangeDetectionStrategy = /*#__PURE__*/ (() => {
94-
ChangeDetectionStrategy = ChangeDetectionStrategy || {};
138+
export var ChangeDetectionStrategy = /*#__PURE__*/ (function (ChangeDetectionStrategy) {
95139
ChangeDetectionStrategy[(ChangeDetectionStrategy["OnPush"] = 5)] = "OnPush";
96140
ChangeDetectionStrategy[(ChangeDetectionStrategy["Default"] = 8)] = "Default";
97141
return ChangeDetectionStrategy;
98-
})();
142+
})(ChangeDetectionStrategy || {});
99143
`,
100144
}),
101145
);
@@ -112,13 +156,12 @@ describe('adjust-typescript-enums Babel plugin', () => {
112156
})(NotificationKind || (NotificationKind = {}));
113157
`,
114158
expected: `
115-
var NotificationKind = /*#__PURE__*/ (() => {
116-
NotificationKind = NotificationKind || {};
159+
var NotificationKind = /*#__PURE__*/ (function (NotificationKind) {
117160
NotificationKind["NEXT"] = "N";
118161
NotificationKind["ERROR"] = "E";
119162
NotificationKind["COMPLETE"] = "C";
120163
return NotificationKind;
121-
})();
164+
})(NotificationKind || {});
122165
`,
123166
}),
124167
);
@@ -135,14 +178,12 @@ describe('adjust-typescript-enums Babel plugin', () => {
135178
})(NotificationKind$1 || (NotificationKind$1 = {}));
136179
`,
137180
expected: `
138-
var NotificationKind$1 = /*#__PURE__*/ (() => {
139-
(function (NotificationKind) {
140-
NotificationKind["NEXT"] = "N";
141-
NotificationKind["ERROR"] = "E";
142-
NotificationKind["COMPLETE"] = "C";
143-
})(NotificationKind$1 || (NotificationKind$1 = {}));
144-
return NotificationKind$1;
145-
})();
181+
var NotificationKind$1 = /*#__PURE__*/ (function (NotificationKind) {
182+
NotificationKind["NEXT"] = "N";
183+
NotificationKind["ERROR"] = "E";
184+
NotificationKind["COMPLETE"] = "C";
185+
return NotificationKind;
186+
})(NotificationKind$1 || {});
146187
`,
147188
}),
148189
);
@@ -171,8 +212,7 @@ describe('adjust-typescript-enums Babel plugin', () => {
171212
* Supported http methods.
172213
* @deprecated use @angular/common/http instead
173214
*/
174-
var RequestMethod = /*#__PURE__*/ (() => {
175-
RequestMethod = RequestMethod || {};
215+
var RequestMethod = /*#__PURE__*/ (function (RequestMethod) {
176216
RequestMethod[(RequestMethod["Get"] = 0)] = "Get";
177217
RequestMethod[(RequestMethod["Post"] = 1)] = "Post";
178218
RequestMethod[(RequestMethod["Put"] = 2)] = "Put";
@@ -181,7 +221,7 @@ describe('adjust-typescript-enums Babel plugin', () => {
181221
RequestMethod[(RequestMethod["Head"] = 5)] = "Head";
182222
RequestMethod[(RequestMethod["Patch"] = 6)] = "Patch";
183223
return RequestMethod;
184-
})();
224+
})(RequestMethod || {});
185225
`,
186226
}),
187227
);
@@ -228,18 +268,17 @@ describe('adjust-typescript-enums Babel plugin', () => {
228268
})(ChangeDetectionStrategy || (ChangeDetectionStrategy = {}));
229269
`,
230270
expected: `
231-
var ChangeDetectionStrategy = /*#__PURE__*/ (() => {
232-
ChangeDetectionStrategy = ChangeDetectionStrategy || {};
271+
var ChangeDetectionStrategy = /*#__PURE__*/ (function (ChangeDetectionStrategy) {
233272
ChangeDetectionStrategy[(ChangeDetectionStrategy["OnPush"] = 0)] = "OnPush";
234273
ChangeDetectionStrategy[(ChangeDetectionStrategy["Default"] = 1)] = "Default";
235274
return ChangeDetectionStrategy;
236-
})();
275+
})(ChangeDetectionStrategy || {});
237276
`,
238277
}),
239278
);
240279

241280
it(
242-
'should not wrap TypeScript enums if the declaration identifier has been renamed to avoid collisions',
281+
'should wrap TypeScript enums if the declaration identifier has been renamed to avoid collisions',
243282
testCase({
244283
input: `
245284
var ChangeDetectionStrategy$1;
@@ -249,13 +288,11 @@ describe('adjust-typescript-enums Babel plugin', () => {
249288
})(ChangeDetectionStrategy$1 || (ChangeDetectionStrategy$1 = {}));
250289
`,
251290
expected: `
252-
var ChangeDetectionStrategy$1 = /*#__PURE__*/ (() => {
253-
(function (ChangeDetectionStrategy) {
254-
ChangeDetectionStrategy[(ChangeDetectionStrategy["OnPush"] = 0)] = "OnPush";
255-
ChangeDetectionStrategy[(ChangeDetectionStrategy["Default"] = 1)] = "Default";
256-
})(ChangeDetectionStrategy$1 || (ChangeDetectionStrategy$1 = {}));
257-
return ChangeDetectionStrategy$1;
258-
})();
291+
var ChangeDetectionStrategy$1 = /*#__PURE__*/ (function (ChangeDetectionStrategy) {
292+
ChangeDetectionStrategy[ChangeDetectionStrategy["OnPush"] = 0] = "OnPush";
293+
ChangeDetectionStrategy[ChangeDetectionStrategy["Default"] = 1] = "Default";
294+
return ChangeDetectionStrategy;
295+
})(ChangeDetectionStrategy$1 || {});
259296
`,
260297
}),
261298
);

0 commit comments

Comments
 (0)