From 3bb67d81fb882d8624c60bf45a4e4990a09e9d16 Mon Sep 17 00:00:00 2001 From: Alan Date: Tue, 18 Jun 2019 08:22:14 +0200 Subject: [PATCH] fix(@angular-devkit/build-optimizer): incorrectly augmented ES2015 default class exports Fixes #14769 --- .../src/transforms/wrap-enums.ts | 53 ++++++++---- .../src/transforms/wrap-enums_spec.ts | 80 +++++++++++++++++++ 2 files changed, 117 insertions(+), 16 deletions(-) diff --git a/packages/angular_devkit/build_optimizer/src/transforms/wrap-enums.ts b/packages/angular_devkit/build_optimizer/src/transforms/wrap-enums.ts index f6149363b9..ef2b94d020 100644 --- a/packages/angular_devkit/build_optimizer/src/transforms/wrap-enums.ts +++ b/packages/angular_devkit/build_optimizer/src/transforms/wrap-enums.ts @@ -63,7 +63,7 @@ function visitBlockStatements( // 'oIndex' is the original statement index; 'uIndex' is the updated statement index for (let oIndex = 0, uIndex = 0; oIndex < statements.length - 1; oIndex++, uIndex++) { const currentStatement = statements[oIndex]; - let newStatement: ts.Statement | undefined; + let newStatement: ts.Statement[] | undefined; let oldStatementsLength = 0; // these can't contain an enum declaration @@ -181,12 +181,15 @@ function visitBlockStatements( oIndex += classStatements.length - 1; } - if (newStatement) { + if (newStatement && newStatement.length > 0) { if (!updatedStatements) { updatedStatements = [...statements]; } - updatedStatements.splice(uIndex, oldStatementsLength, newStatement); + updatedStatements.splice(uIndex, oldStatementsLength, ...newStatement); + // When having more than a single new statement + // we need to update the update Index + uIndex += (newStatement ? newStatement.length - 1 : 0); } const result = ts.visitNode(currentStatement, visitor); @@ -531,7 +534,7 @@ function updateEnumIife( hostNode: ts.VariableStatement, iife: ts.CallExpression, exportAssignment?: ts.Expression, -): ts.Statement { +): ts.Statement[] { if (!ts.isParenthesizedExpression(iife.expression) || !ts.isFunctionExpression(iife.expression.expression)) { throw new Error('Invalid IIFE Structure'); @@ -583,7 +586,7 @@ function updateEnumIife( updatedIife); } - return updateHostNode(hostNode, value); + return [updateHostNode(hostNode, value)]; } function createWrappedEnum( @@ -592,7 +595,7 @@ function createWrappedEnum( statements: Array, literalInitializer: ts.ObjectLiteralExpression = ts.createObjectLiteral(), addExportModifier = false, -): ts.Statement { +): ts.Statement[] { const node = addExportModifier ? ts.updateVariableStatement( hostNode, @@ -616,13 +619,13 @@ function createWrappedEnum( innerReturn, ]); - return updateHostNode(node, addPureComment(ts.createParen(iife))); + return [updateHostNode(node, addPureComment(ts.createParen(iife)))]; } function createWrappedClass( hostNode: ts.ClassDeclaration | ts.VariableDeclaration, statements: ts.Statement[], -): ts.Statement { +): ts.Statement[] { const name = (hostNode.name as ts.Identifier).text; const updatedStatements = [...statements]; @@ -645,12 +648,30 @@ function createWrappedClass( ]), ); - return ts.createVariableStatement( - hostNode.modifiers, - ts.createVariableDeclarationList([ - ts.createVariableDeclaration(name, undefined, pureIife), - ], - ts.NodeFlags.Const, - ), - ); + const modifiers = hostNode.modifiers; + const isDefault = !!modifiers + && modifiers.some(x => x.kind === ts.SyntaxKind.DefaultKeyword); + + const newStatement: ts.Statement[] = []; + newStatement.push( + ts.createVariableStatement( + isDefault ? undefined : modifiers, + ts.createVariableDeclarationList([ + ts.createVariableDeclaration(name, undefined, pureIife), + ], + ts.NodeFlags.Const, + ), + )); + + if (isDefault) { + newStatement.push( + ts.createExportAssignment( + undefined, + undefined, + false, + ts.createIdentifier(name), + )); + } + + return newStatement; } diff --git a/packages/angular_devkit/build_optimizer/src/transforms/wrap-enums_spec.ts b/packages/angular_devkit/build_optimizer/src/transforms/wrap-enums_spec.ts index e9881adf3c..8f6547274b 100644 --- a/packages/angular_devkit/build_optimizer/src/transforms/wrap-enums_spec.ts +++ b/packages/angular_devkit/build_optimizer/src/transforms/wrap-enums_spec.ts @@ -17,6 +17,46 @@ const transform = (content: string) => transformJavascript( // tslint:disable:no-big-function describe('wrap enums and classes transformer', () => { describe('wraps class declarations', () => { + it('should wrap default exported classes', () => { + const defaultClass = tags.stripIndent` + export default class CustomComponentEffects { + constructor(_actions) { + this._actions = _actions; + this.doThis = this._actions; + } + } + CustomComponentEffects.decorators = [{ type: Injectable }]; + `; + + const namedClass = tags.stripIndent` + class CustomComponent { + constructor(_actions) { + this._actions = _actions; + this.doThis = this._actions; + } + } + CustomComponent.decorators = [{ type: Injectable }]; + `; + + const output = tags.stripIndent` + const CustomComponentEffects = /*@__PURE__*/ (() => { + ${defaultClass.replace('export default ', '')} + + return CustomComponentEffects; + })(); + export default CustomComponentEffects; + + const CustomComponent = /*@__PURE__*/ (() => { + ${namedClass} + + return CustomComponent; + })(); + `; + + const input = defaultClass + namedClass; + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); + }); + it('should wrap tsickle emitted classes which followed by metadata', () => { const input = tags.stripIndent` class CustomComponentEffects { @@ -169,6 +209,46 @@ describe('wrap enums and classes transformer', () => { }); describe('wrap class expressions', () => { + it('should wrap default exported classes', () => { + const defaultClass = tags.stripIndent` + let Foo = class Foo { + }; + Foo.bar = 'bar'; + Foo = __decorate([ + component() + ], Foo); + export default Foo; + `; + + const namedClass = tags.stripIndent` + let AggregateColumnDirective = class AggregateColumnDirective { + constructor(viewContainerRef) { } + }; + AggregateColumnDirective = __decorate([ + Directive({}), + __metadata("design:paramtypes", [ViewContainerRef]) + ], AggregateColumnDirective); + `; + + const output = tags.stripIndent` + const Foo = /*@__PURE__*/ (() => { + ${defaultClass.replace('export default Foo;', '')} + + return Foo; + })(); + export default Foo; + + const AggregateColumnDirective = /*@__PURE__*/ (() => { + ${namedClass} + + return AggregateColumnDirective; + })(); + `; + + const input = defaultClass + namedClass; + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); + }); + it('without property decorators in IIFE', () => { const input = tags.stripIndent` let AggregateColumnDirective = class AggregateColumnDirective {