Skip to content

Commit 2676c2d

Browse files
alan-agius4mgechev
authored andcommitted
refactor(@angular-devkit/build-optimizer): refactor wrap enums
Most of the logic that can find class statements to wrap can be used to wrap Enums, with the exception of TS 2.3+ enums which is slightly different. This PR combines the enums and classes lookup logic and also simplifies the TS 2.3+ enum lookup logic
1 parent afdd211 commit 2676c2d

File tree

1 file changed

+61
-216
lines changed
  • packages/angular_devkit/build_optimizer/src/transforms

1 file changed

+61
-216
lines changed

packages/angular_devkit/build_optimizer/src/transforms/wrap-enums.ts

Lines changed: 61 additions & 216 deletions
Original file line numberDiff line numberDiff line change
@@ -104,54 +104,38 @@ function visitBlockStatements(
104104
// skip IIFE statement
105105
oIndex++;
106106
}
107-
} else if (ts.isObjectLiteralExpression(initializer)
108-
&& initializer.properties.length === 0) {
109-
const enumStatements = findTs2_2EnumStatements(name, statements, oIndex + 1);
110-
if (enumStatements.length > 0) {
111-
// create wrapper and replace variable statement and enum member statements
112-
oldStatementsLength = enumStatements.length + 1;
113-
newStatement = createWrappedEnum(
114-
name,
115-
currentStatement,
116-
enumStatements,
117-
initializer,
118-
);
119-
// skip enum member declarations
120-
oIndex += enumStatements.length;
121-
}
122-
} else if (ts.isObjectLiteralExpression(initializer)
123-
&& initializer.properties.length !== 0) {
124-
const literalPropertyCount = initializer.properties.length;
125-
107+
} else if (ts.isObjectLiteralExpression(initializer)) {
126108
// tsickle es2015 enums first statement is an export declaration
127109
const isPotentialEnumExport = ts.isExportDeclaration(statements[oIndex + 1]);
128110
if (isPotentialEnumExport) {
129111
// skip the export
130-
oIndex ++;
112+
oIndex++;
131113
}
132114

133-
const enumStatements = findEnumNameStatements(name, statements, oIndex + 1);
134-
if (enumStatements.length === literalPropertyCount) {
135-
// create wrapper and replace variable statement and enum member statements
136-
oldStatementsLength = enumStatements.length + (isPotentialEnumExport ? 2 : 1);
137-
newStatement = createWrappedEnum(
138-
name,
139-
currentStatement,
140-
enumStatements,
141-
initializer,
142-
isPotentialEnumExport,
143-
);
144-
// skip enum member declarations
145-
oIndex += enumStatements.length;
115+
const enumStatements = findStatements(name, statements, oIndex + 1);
116+
if (!enumStatements) {
117+
continue;
146118
}
119+
120+
// create wrapper and replace variable statement and enum member statements
121+
oldStatementsLength = enumStatements.length + (isPotentialEnumExport ? 2 : 1);
122+
newStatement = createWrappedEnum(
123+
name,
124+
currentStatement,
125+
enumStatements,
126+
initializer,
127+
isPotentialEnumExport,
128+
);
129+
// skip enum member declarations
130+
oIndex += enumStatements.length;
147131
} else if (
148132
ts.isClassExpression(initializer)
149133
|| (
150134
ts.isBinaryExpression(initializer)
151135
&& ts.isClassExpression(initializer.right)
152136
)
153137
) {
154-
const classStatements = findClassStatements(name, statements, oIndex);
138+
const classStatements = findStatements(name, statements, oIndex);
155139
if (!classStatements) {
156140
continue;
157141
}
@@ -167,7 +151,7 @@ function visitBlockStatements(
167151
}
168152
} else if (ts.isClassDeclaration(currentStatement)) {
169153
const name = (currentStatement.name as ts.Identifier).text;
170-
const classStatements = findClassStatements(name, statements, oIndex);
154+
const classStatements = findStatements(name, statements, oIndex);
171155
if (!classStatements) {
172156
continue;
173157
}
@@ -215,31 +199,43 @@ function findTs2_3EnumIife(
215199
return null;
216200
}
217201

218-
let expression = statement.expression;
219-
while (ts.isParenthesizedExpression(expression)) {
220-
expression = expression.expression;
221-
}
222-
202+
const expression = statement.expression;
223203
if (!expression || !ts.isCallExpression(expression) || expression.arguments.length !== 1) {
224204
return null;
225205
}
226206

227207
const callExpression = expression;
228-
let exportExpression;
208+
let exportExpression: ts.Expression | undefined;
229209

230-
let argument = expression.arguments[0];
231-
if (!ts.isBinaryExpression(argument)) {
210+
if (!ts.isParenthesizedExpression(callExpression.expression)) {
211+
return null;
212+
}
213+
214+
const functionExpression = callExpression.expression.expression;
215+
if (!ts.isFunctionExpression(functionExpression)) {
216+
return null;
217+
}
218+
219+
// The name of the parameter can be different than the name of the enum if it was renamed
220+
// due to scope hoisting.
221+
const parameter = functionExpression.parameters[0];
222+
if (!ts.isIdentifier(parameter.name)) {
232223
return null;
233224
}
225+
const parameterName = parameter.name.text;
234226

235-
if (!ts.isIdentifier(argument.left) || argument.left.text !== name) {
227+
let argument = callExpression.arguments[0];
228+
if (
229+
!ts.isBinaryExpression(argument)
230+
|| !ts.isIdentifier(argument.left)
231+
|| argument.left.text !== name
232+
) {
236233
return null;
237234
}
238235

239236
let potentialExport = false;
240237
if (argument.operatorToken.kind === ts.SyntaxKind.FirstAssignment) {
241-
if (!ts.isBinaryExpression(argument.right)
242-
|| argument.right.operatorToken.kind !== ts.SyntaxKind.BarBarToken) {
238+
if (ts.isBinaryExpression(argument.right) && argument.right.operatorToken.kind !== ts.SyntaxKind.BarBarToken) {
243239
return null;
244240
}
245241

@@ -259,177 +255,25 @@ function findTs2_3EnumIife(
259255
exportExpression = argument.left;
260256
}
261257

262-
expression = expression.expression;
263-
while (ts.isParenthesizedExpression(expression)) {
264-
expression = expression.expression;
265-
}
266-
267-
if (!expression || !ts.isFunctionExpression(expression) || expression.parameters.length !== 1) {
268-
return null;
269-
}
270-
271-
const parameter = expression.parameters[0];
272-
if (!ts.isIdentifier(parameter.name)) {
273-
return null;
274-
}
275-
276-
// The name of the parameter can be different than the name of the enum if it was renamed
277-
// due to scope hoisting.
278-
const parameterName = parameter.name.text;
279-
280-
// In TS 2.3 enums, the IIFE contains only expressions with a certain format.
281-
// If we find any that is different, we ignore the whole thing.
282-
for (let bodyIndex = 0; bodyIndex < expression.body.statements.length; ++bodyIndex) {
283-
const bodyStatement = expression.body.statements[bodyIndex];
284-
285-
if (!ts.isExpressionStatement(bodyStatement) || !bodyStatement.expression) {
286-
return null;
287-
}
288-
289-
if (!ts.isBinaryExpression(bodyStatement.expression)
290-
|| bodyStatement.expression.operatorToken.kind !== ts.SyntaxKind.FirstAssignment) {
291-
return null;
292-
}
293-
294-
const assignment = bodyStatement.expression.left;
295-
const value = bodyStatement.expression.right;
296-
if (!ts.isElementAccessExpression(assignment) || !ts.isStringLiteral(value)) {
297-
return null;
298-
}
299-
300-
if (!ts.isIdentifier(assignment.expression) || assignment.expression.text !== parameterName) {
301-
return null;
302-
}
303-
304-
const memberArgument = assignment.argumentExpression;
305-
// String enum
306-
if (ts.isStringLiteral(memberArgument)) {
307-
return [callExpression, exportExpression];
308-
}
309-
310-
// Non string enums
311-
if (!ts.isBinaryExpression(memberArgument)
312-
|| memberArgument.operatorToken.kind !== ts.SyntaxKind.FirstAssignment) {
313-
return null;
314-
}
315-
316-
if (!ts.isElementAccessExpression(memberArgument.left)) {
317-
return null;
318-
}
319-
320-
if (!ts.isIdentifier(memberArgument.left.expression)
321-
|| memberArgument.left.expression.text !== parameterName) {
322-
return null;
258+
// Go through all the statements and check that all match the name
259+
for (const statement of functionExpression.body.statements) {
260+
if (
261+
!ts.isExpressionStatement(statement)
262+
|| !ts.isBinaryExpression(statement.expression)
263+
|| !ts.isElementAccessExpression(statement.expression.left)
264+
) {
265+
return null
323266
}
324267

325-
if (!memberArgument.left.argumentExpression
326-
|| !ts.isStringLiteral(memberArgument.left.argumentExpression)) {
327-
return null;
328-
}
329-
330-
if (memberArgument.left.argumentExpression.text !== value.text) {
268+
const leftExpression = statement.expression.left.expression;
269+
if (!ts.isIdentifier(leftExpression) || leftExpression.text !== parameterName) {
331270
return null;
332271
}
333272
}
334273

335274
return [callExpression, exportExpression];
336275
}
337276

338-
// TS 2.2 enums have statements after the variable declaration, with index statements followed
339-
// by value statements.
340-
function findTs2_2EnumStatements(
341-
name: string,
342-
statements: ts.NodeArray<ts.Statement>,
343-
statementOffset: number,
344-
): ts.Statement[] {
345-
const enumValueStatements: ts.Statement[] = [];
346-
const memberNames: string[] = [];
347-
348-
let index = statementOffset;
349-
for (; index < statements.length; ++index) {
350-
// Ensure all statements are of the expected format and using the right identifer.
351-
// When we find a statement that isn't part of the enum, return what we collected so far.
352-
const current = statements[index];
353-
if (!ts.isExpressionStatement(current) || !ts.isBinaryExpression(current.expression)) {
354-
break;
355-
}
356-
357-
const property = current.expression.left;
358-
if (!property || !ts.isPropertyAccessExpression(property)) {
359-
break;
360-
}
361-
362-
if (!ts.isIdentifier(property.expression) || property.expression.text !== name) {
363-
break;
364-
}
365-
366-
memberNames.push(property.name.text);
367-
enumValueStatements.push(current);
368-
}
369-
370-
if (enumValueStatements.length === 0) {
371-
return [];
372-
}
373-
374-
const enumNameStatements = findEnumNameStatements(name, statements, index, memberNames);
375-
if (enumNameStatements.length !== enumValueStatements.length) {
376-
return [];
377-
}
378-
379-
return enumValueStatements.concat(enumNameStatements);
380-
}
381-
382-
// Tsickle enums have a variable statement with indexes, followed by value statements.
383-
// See https://github.com/angular/devkit/issues/229#issuecomment-338512056 fore more information.
384-
function findEnumNameStatements(
385-
name: string,
386-
statements: ts.NodeArray<ts.Statement>,
387-
statementOffset: number,
388-
memberNames?: string[],
389-
): ts.Statement[] {
390-
const enumStatements: ts.Statement[] = [];
391-
392-
for (let index = statementOffset; index < statements.length; ++index) {
393-
// Ensure all statements are of the expected format and using the right identifer.
394-
// When we find a statement that isn't part of the enum, return what we collected so far.
395-
const current = statements[index];
396-
if (!ts.isExpressionStatement(current) || !ts.isBinaryExpression(current.expression)) {
397-
break;
398-
}
399-
400-
const access = current.expression.left;
401-
const value = current.expression.right;
402-
if (!access || !ts.isElementAccessExpression(access) || !value || !ts.isStringLiteral(value)) {
403-
break;
404-
}
405-
406-
if (memberNames && !memberNames.includes(value.text)) {
407-
break;
408-
}
409-
410-
if (!ts.isIdentifier(access.expression) || access.expression.text !== name) {
411-
break;
412-
}
413-
414-
if (!access.argumentExpression || !ts.isPropertyAccessExpression(access.argumentExpression)) {
415-
break;
416-
}
417-
418-
const enumExpression = access.argumentExpression.expression;
419-
if (!ts.isIdentifier(enumExpression) || enumExpression.text !== name) {
420-
break;
421-
}
422-
423-
if (value.text !== access.argumentExpression.name.text) {
424-
break;
425-
}
426-
427-
enumStatements.push(current);
428-
}
429-
430-
return enumStatements;
431-
}
432-
433277
function updateHostNode(
434278
hostNode: ts.VariableStatement,
435279
expression: ts.Expression,
@@ -456,7 +300,7 @@ function updateHostNode(
456300
}
457301

458302
/**
459-
* Find class expression or declaration statements.
303+
* Find enums, class expression or declaration statements.
460304
*
461305
* The classExpressions block to wrap in an iife must
462306
* - end with an ExpressionStatement
@@ -468,7 +312,7 @@ function updateHostNode(
468312
Foo = __decorate([]);
469313
```
470314
*/
471-
function findClassStatements(
315+
function findStatements(
472316
name: string,
473317
statements: ts.NodeArray<ts.Statement>,
474318
statementIndex: number,
@@ -484,11 +328,11 @@ function findClassStatements(
484328
const expression = statement.expression;
485329

486330
if (ts.isCallExpression(expression)) {
487-
// Ex:
488-
// setClassMetadata(FooClass, [{}], void 0);
489-
// __decorate([propDecorator()], FooClass.prototype, "propertyName", void 0);
490-
// __decorate([propDecorator()], FooClass, "propertyName", void 0);
491-
// __decorate$1([propDecorator()], FooClass, "propertyName", void 0);
331+
// Ex:
332+
// setClassMetadata(FooClass, [{}], void 0);
333+
// __decorate([propDecorator()], FooClass.prototype, "propertyName", void 0);
334+
// __decorate([propDecorator()], FooClass, "propertyName", void 0);
335+
// __decorate$1([propDecorator()], FooClass, "propertyName", void 0);
492336
const args = expression.arguments;
493337

494338
if (args.length > 2) {
@@ -508,8 +352,9 @@ function findClassStatements(
508352
? expression.left.left
509353
: expression.left;
510354

511-
const leftExpression = ts.isPropertyAccessExpression(node)
355+
const leftExpression = ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)
512356
// Static Properties // Ex: Foo.bar = 'value';
357+
// ENUM Property // Ex: ChangeDetectionStrategy[ChangeDetectionStrategy.Default] = "Default";
513358
? node.expression
514359
// Ex: FooClass = __decorate([Component()], FooClass);
515360
: node;

0 commit comments

Comments
 (0)