diff --git a/packages/compiler-cli/ngcc/src/rendering/umd_rendering_formatter.ts b/packages/compiler-cli/ngcc/src/rendering/umd_rendering_formatter.ts index 533fd0292fb..ebbc3e0b3dd 100644 --- a/packages/compiler-cli/ngcc/src/rendering/umd_rendering_formatter.ts +++ b/packages/compiler-cli/ngcc/src/rendering/umd_rendering_formatter.ts @@ -61,13 +61,13 @@ export class UmdRenderingFormatter extends Esm5RenderingFormatter { return; } - const wrapperFunction = umdModule.wrapperFn; + const {wrapperFn, factoryFn} = umdModule; // We need to add new `require()` calls for each import in the CommonJS initializer - renderCommonJsDependencies(output, wrapperFunction, imports); - renderAmdDependencies(output, wrapperFunction, imports); - renderGlobalDependencies(output, wrapperFunction, imports); - renderFactoryParameters(output, wrapperFunction, imports); + renderCommonJsDependencies(output, wrapperFn, imports); + renderAmdDependencies(output, wrapperFn, imports); + renderGlobalDependencies(output, wrapperFn, imports); + renderFactoryParameters(output, factoryFn, imports); } /** @@ -210,20 +210,7 @@ function renderGlobalDependencies( * Add dependency parameters to the UMD factory function. */ function renderFactoryParameters( - output: MagicString, wrapperFunction: ts.FunctionExpression, imports: Import[]) { - const wrapperCall = wrapperFunction.parent as ts.CallExpression; - const secondArgument = wrapperCall.arguments[1]; - if (!secondArgument) { - return; - } - - // Be resilient to the factory being inside parentheses - const factoryFunction = - ts.isParenthesizedExpression(secondArgument) ? secondArgument.expression : secondArgument; - if (!ts.isFunctionExpression(factoryFunction)) { - return; - } - + output: MagicString, factoryFunction: ts.FunctionExpression, imports: Import[]) { const parameters = factoryFunction.parameters; const parameterString = imports.map(i => i.qualifier.text).join(','); if (parameters.length > 0) { diff --git a/packages/compiler-cli/ngcc/test/rendering/umd_rendering_formatter_spec.ts b/packages/compiler-cli/ngcc/test/rendering/umd_rendering_formatter_spec.ts index b663b0b31e0..f4e16656947 100644 --- a/packages/compiler-cli/ngcc/test/rendering/umd_rendering_formatter_spec.ts +++ b/packages/compiler-cli/ngcc/test/rendering/umd_rendering_formatter_spec.ts @@ -22,6 +22,10 @@ import {UmdReflectionHost} from '../../src/host/umd_host'; import {UmdRenderingFormatter} from '../../src/rendering/umd_rendering_formatter'; import {makeTestEntryPointBundle} from '../helpers/utils'; +interface TestFileSpec extends Omit { + contents: {preamble?: string; wrapperFunction: string; wrapperCallArguments: string;}; +} + function setup(file: TestFile) { loadTestFiles([file]); const fs = getFileSystem(); @@ -50,32 +54,55 @@ function setup(file: TestFile) { runInEachFileSystem(() => { describe('UmdRenderingFormatter', () => { let _: typeof absoluteFrom; - let PROGRAM: TestFile; - let PROGRAM_DECORATE_HELPER: TestFile; - let PROGRAM_WITH_GLOBAL_INITIALIZER: TestFile; + let PROGRAM_FILE_SPEC: TestFileSpec; + let PROGRAM_DECORATE_HELPER_FILE_SPEC: TestFileSpec; + let PROGRAM_WITH_GLOBAL_INITIALIZER_FILE_SPEC: TestFileSpec; + + // Factories for creating a `TestFile` from a `TestFileSpec` for different UMD formats. + const umdFormatFactories: Record TestFile> = { + // Old format (parenthesis around call expression): `(function (...) { ... }(...))` + 'old format': spec => ({ + ...spec, + contents: `${spec.contents.preamble ?? ''}\n` + + `(${spec.contents.wrapperFunction}(${spec.contents.wrapperCallArguments}));`, + }), + + // New format (parenthesis around function expression): `(function (...) { ... })(...)` + 'new format': spec => ({ + ...spec, + contents: `${spec.contents.preamble ?? ''}\n` + + `(${spec.contents.wrapperFunction})(${spec.contents.wrapperCallArguments});`, + }), + }; beforeEach(() => { _ = absoluteFrom; - PROGRAM_WITH_GLOBAL_INITIALIZER = { + PROGRAM_WITH_GLOBAL_INITIALIZER_FILE_SPEC = { name: _('/node_modules/test-package/some/file.js'), - contents: ` - (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports,require('some-side-effect'),require('/local-dep'),require('@angular/core')) : - typeof define === 'function' && define.amd ? define('file', ['exports','some-side-effect','/local-dep','@angular/core'], factory) : - (global = global || self, factory(global.file,global.someSideEffect,global.localDep,global.ng.core)); - }(this, (function (exports,someSideEffect,localDep,core) {'use strict'; })));` + contents: { + wrapperFunction: `function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports,require('some-side-effect'),require('/local-dep'),require('@angular/core')) : + typeof define === 'function' && define.amd ? define('file', ['exports','some-side-effect','/local-dep','@angular/core'], factory) : + (global = global || self, factory(global.file,global.someSideEffect,global.localDep,global.ng.core)); + }`, + wrapperCallArguments: + `this, (function (exports,someSideEffect,localDep,core) {'use strict'; })`, + }, }; - PROGRAM = { + PROGRAM_FILE_SPEC = { name: _('/node_modules/test-package/some/file.js'), - contents: ` -/* A copyright notice */ -(function (global, factory) { + contents: { + preamble: ` +/* A copyright notice */`, + wrapperFunction: `function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports,require('some-side-effect'),require('/local-dep'),require('@angular/core')) : typeof define === 'function' && define.amd ? define('file', ['exports','some-side-effect','/local-dep','@angular/core'], factory) : (factory(global.file,global.someSideEffect,global.localDep,global.ng.core)); -}(this, (function (exports,someSideEffect,localDep,core) {'use strict'; +}`, + wrapperCallArguments: + `this, (function (exports,someSideEffect,localDep,core) {'use strict'; var A = (function() { function A() {} A.decorators = [ @@ -132,19 +159,22 @@ exports.B = B; exports.C = C; exports.NoIife = NoIife; exports.BadIife = BadIife; -})));`, +})`, + }, }; - PROGRAM_DECORATE_HELPER = { + PROGRAM_DECORATE_HELPER_FILE_SPEC = { name: _('/node_modules/test-package/some/file.js'), - contents: ` -/* A copyright notice */ -(function (global, factory) { + contents: { + preamble: ` +/* A copyright notice */`, + wrapperFunction: `function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports,require('tslib'),require('@angular/core')) : typeof define === 'function' && define.amd ? define('file', ['exports','/tslib','@angular/core'], factory) : (factory(global.file,global.tslib,global.ng.core)); -}(this, (function (exports,tslib,core) {'use strict'; +}`, + wrapperCallArguments: `this, (function (exports,tslib,core) {'use strict'; var OtherA = function () { return function (node) { }; }; var OtherB = function () { return function (node) { }; }; var A = /** @class */ (function () { @@ -188,216 +218,246 @@ typeof define === 'function' && define.amd ? define('file', ['exports','/tslib', }()); exports.D = D; // Some other content -})));` +})`, + }, }; }); - describe('addImports', () => { - it('should append the given imports into the CommonJS factory call', () => { - const {renderer, program} = setup(PROGRAM); - const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); - const output = new MagicString(PROGRAM.contents); - renderer.addImports( - output, - [ - {specifier: '@angular/core', qualifier: ts.createIdentifier('i0')}, - {specifier: '@angular/common', qualifier: ts.createIdentifier('i1')} - ], - file); - expect(output.toString()) - .toContain( - `typeof exports === 'object' && typeof module !== 'undefined' ? ` + - `factory(require('@angular/core'),require('@angular/common'),exports,require('some-side-effect'),require('/local-dep'),require('@angular/core')) :`); - }); + Object.entries(umdFormatFactories).forEach(([formatLabel, formatFactory]) => { + describe(`(when dealing with ${formatLabel})`, () => { + let PROGRAM: TestFile; + let PROGRAM_DECORATE_HELPER: TestFile; + let PROGRAM_WITH_GLOBAL_INITIALIZER: TestFile; - it('should append the given imports into the AMD initialization', () => { - const {renderer, program} = setup(PROGRAM); - const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); - const output = new MagicString(PROGRAM.contents); - renderer.addImports( - output, - [ - {specifier: '@angular/core', qualifier: ts.createIdentifier('i0')}, - {specifier: '@angular/common', qualifier: ts.createIdentifier('i1')} - ], - file); - expect(output.toString()) - .toContain( - `typeof define === 'function' && define.amd ? define('file', ['@angular/core','@angular/common','exports','some-side-effect','/local-dep','@angular/core'], factory) :`); - }); + beforeEach(() => { + PROGRAM = formatFactory(PROGRAM_FILE_SPEC); + PROGRAM_DECORATE_HELPER = formatFactory(PROGRAM_DECORATE_HELPER_FILE_SPEC); + PROGRAM_WITH_GLOBAL_INITIALIZER = + formatFactory(PROGRAM_WITH_GLOBAL_INITIALIZER_FILE_SPEC); + }); - it('should append the given imports into the global initialization', () => { - const {renderer, program} = setup(PROGRAM); - const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); - const output = new MagicString(PROGRAM.contents); - renderer.addImports( - output, - [ - {specifier: '@angular/core', qualifier: ts.createIdentifier('i0')}, - {specifier: '@angular/common', qualifier: ts.createIdentifier('i1')} - ], - file); - expect(output.toString()) - .toContain( - `(factory(global.ng.core,global.ng.common,global.file,global.someSideEffect,global.localDep,global.ng.core));`); - }); + describe('addImports', () => { + it('should append the given imports into the CommonJS factory call', () => { + const {renderer, program} = setup(PROGRAM); + const file = + getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); + const output = new MagicString(PROGRAM.contents); + renderer.addImports( + output, + [ + {specifier: '@angular/core', qualifier: ts.createIdentifier('i0')}, + {specifier: '@angular/common', qualifier: ts.createIdentifier('i1')} + ], + file); + expect(output.toString()) + .toContain( + `typeof exports === 'object' && typeof module !== 'undefined' ? ` + + `factory(require('@angular/core'),require('@angular/common'),exports,require('some-side-effect'),require('/local-dep'),require('@angular/core')) :`); + }); - it('should remap import identifiers to valid global properties', () => { - const {renderer, program} = setup(PROGRAM); - const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); - const output = new MagicString(PROGRAM.contents); - renderer.addImports( - output, - [ - {specifier: '@ngrx/store', qualifier: ts.createIdentifier('i0')}, { - specifier: '@angular/platform-browser-dynamic', - qualifier: ts.createIdentifier('i1') + it('should append the given imports into the AMD initialization', () => { + const {renderer, program} = setup(PROGRAM); + const file = + getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); + const output = new MagicString(PROGRAM.contents); + renderer.addImports( + output, + [ + {specifier: '@angular/core', qualifier: ts.createIdentifier('i0')}, + {specifier: '@angular/common', qualifier: ts.createIdentifier('i1')} + ], + file); + expect(output.toString()) + .toContain( + `typeof define === 'function' && define.amd ? define('file', ['@angular/core','@angular/common','exports','some-side-effect','/local-dep','@angular/core'], factory) :`); + }); + + it('should append the given imports into the global initialization', () => { + const {renderer, program} = setup(PROGRAM); + const file = + getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); + const output = new MagicString(PROGRAM.contents); + renderer.addImports( + output, + [ + {specifier: '@angular/core', qualifier: ts.createIdentifier('i0')}, + {specifier: '@angular/common', qualifier: ts.createIdentifier('i1')} + ], + file); + expect(output.toString()) + .toContain( + `(factory(global.ng.core,global.ng.common,global.file,global.someSideEffect,global.localDep,global.ng.core));`); + }); + + it('should remap import identifiers to valid global properties', () => { + const {renderer, program} = setup(PROGRAM); + const file = + getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); + const output = new MagicString(PROGRAM.contents); + renderer.addImports( + output, + [ + {specifier: '@ngrx/store', qualifier: ts.createIdentifier('i0')}, { + specifier: '@angular/platform-browser-dynamic', + qualifier: ts.createIdentifier('i1') + }, + {specifier: '@angular/common/testing', qualifier: ts.createIdentifier('i2')}, + {specifier: '@angular-foo/package', qualifier: ts.createIdentifier('i3')} + ], + file); + expect(output.toString()) + .toContain( + `(factory(` + + `global.ngrx.store,global.ng.platformBrowserDynamic,global.ng.common.testing,global.angularFoo.package,` + + `global.file,global.someSideEffect,global.localDep,global.ng.core));`); + }); + + it('should append the given imports into the global initialization, if it has a global/self initializer', + () => { + const {renderer, program} = setup(PROGRAM_WITH_GLOBAL_INITIALIZER); + const file = + getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); + const output = new MagicString(file.text); + renderer.addImports( + output, + [ + {specifier: '@angular/core', qualifier: ts.createIdentifier('i0')}, + {specifier: '@angular/common', qualifier: ts.createIdentifier('i1')} + ], + file); + expect(output.toString()) + .toContain( + `(global = global || self, factory(global.ng.core,global.ng.common,global.file,global.someSideEffect,global.localDep,global.ng.core));`); + }); + + it('should append the given imports as parameters into the factory function definition', + () => { + const {renderer, program} = setup(PROGRAM); + const file = + getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); + const output = new MagicString(PROGRAM.contents); + renderer.addImports( + output, + [ + {specifier: '@angular/core', qualifier: ts.createIdentifier('i0')}, + {specifier: '@angular/common', qualifier: ts.createIdentifier('i1')} + ], + file); + expect(output.toString()) + .toContain( + `(function (i0,i1,exports,someSideEffect,localDep,core) {'use strict';`); + }); + + it('should handle the case where there were no prior imports nor exports', () => { + const PROGRAM = formatFactory({ + name: _('/node_modules/test-package/some/file.js'), + contents: { + preamble: ` + /* A copyright notice */`, + wrapperFunction: `function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define('file', factory) : + (factory()); + }`, + wrapperCallArguments: `this, (function () {'use strict'; + var index = ''; + return index; + })`, }, - {specifier: '@angular/common/testing', qualifier: ts.createIdentifier('i2')}, - {specifier: '@angular-foo/package', qualifier: ts.createIdentifier('i3')} - ], - file); - expect(output.toString()) - .toContain( - `(factory(` + - `global.ngrx.store,global.ng.platformBrowserDynamic,global.ng.common.testing,global.angularFoo.package,` + - `global.file,global.someSideEffect,global.localDep,global.ng.core));`); - }); + }); + const {renderer, program} = setup(PROGRAM); + const file = + getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); + const output = new MagicString(PROGRAM.contents); + renderer.addImports( + output, + [ + {specifier: '@angular/core', qualifier: ts.createIdentifier('i0')}, + {specifier: '@angular/common', qualifier: ts.createIdentifier('i1')} + ], + file); + const outputSrc = output.toString(); - it('should append the given imports into the global initialization, if it has a global/self initializer', - () => { - const {renderer, program} = setup(PROGRAM_WITH_GLOBAL_INITIALIZER); - const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); - const output = new MagicString(file.text); - renderer.addImports( - output, - [ - {specifier: '@angular/core', qualifier: ts.createIdentifier('i0')}, - {specifier: '@angular/common', qualifier: ts.createIdentifier('i1')} - ], - file); - expect(output.toString()) - .toContain( - `(global = global || self, factory(global.ng.core,global.ng.common,global.file,global.someSideEffect,global.localDep,global.ng.core));`); - }); + expect(outputSrc).toContain( + `typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('@angular/core'),require('@angular/common')) :`); + expect(outputSrc).toContain( + `typeof define === 'function' && define.amd ? define('file',['@angular/core','@angular/common'], factory) :`); + expect(outputSrc).toContain(`(factory(global.ng.core,global.ng.common));`); + expect(outputSrc).toContain(`(function (i0,i1) {'use strict';`); + }); - it('should append the given imports as parameters into the factory function definition', - () => { - const {renderer, program} = setup(PROGRAM); - const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); - const output = new MagicString(PROGRAM.contents); - renderer.addImports( - output, - [ - {specifier: '@angular/core', qualifier: ts.createIdentifier('i0')}, - {specifier: '@angular/common', qualifier: ts.createIdentifier('i1')} - ], - file); - expect(output.toString()) - .toContain(`(function (i0,i1,exports,someSideEffect,localDep,core) {'use strict';`); - }); + it('should leave the file unchanged if there are no imports to add', () => { + const {renderer, program} = setup(PROGRAM); + const file = + getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); + const output = new MagicString(PROGRAM.contents); + const contentsBefore = output.toString(); - it('should handle the case where there were no prior imports nor exports', () => { - const PROGRAM: TestFile = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` - /* A copyright notice */ - (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory() : - typeof define === 'function' && define.amd ? define('file', factory) : - (factory()); - }(this, (function () {'use strict'; - var index = ''; - return index; - })));`, - }; - const {renderer, program} = setup(PROGRAM); - const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); - const output = new MagicString(PROGRAM.contents); - renderer.addImports( - output, - [ - {specifier: '@angular/core', qualifier: ts.createIdentifier('i0')}, - {specifier: '@angular/common', qualifier: ts.createIdentifier('i1')} - ], - file); - const outputSrc = output.toString(); + renderer.addImports(output, [], file); + const contentsAfter = output.toString(); - expect(outputSrc).toContain( - `typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('@angular/core'),require('@angular/common')) :`); - expect(outputSrc).toContain( - `typeof define === 'function' && define.amd ? define('file',['@angular/core','@angular/common'], factory) :`); - expect(outputSrc).toContain(`(factory(global.ng.core,global.ng.common));`); - expect(outputSrc).toContain(`(function (i0,i1) {'use strict';`); - }); + expect(contentsAfter).toBe(contentsBefore); + }); - it('should leave the file unchanged if there are no imports to add', () => { - const {renderer, program} = setup(PROGRAM); - const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); - const output = new MagicString(PROGRAM.contents); - const contentsBefore = output.toString(); + it('should handle the case where not all dependencies are used by the factory', () => { + const PROGRAM = formatFactory({ + name: _('/node_modules/test-package/some/file.js'), + contents: { + preamble: ` + /* A copyright notice */ + /* A copyright notice */`, + wrapperFunction: `function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports,require('/local-dep'),require('@angular/core'),require('some-side-effect')) : + typeof define === 'function' && define.amd ? define('file', ['exports','/local-dep','@angular/core','some-side-effect'], factory) : + (factory(global.file,global.localDep,global.ng.core,global.someSideEffect)); + }`, + wrapperCallArguments: `this, (function (exports,localDep,core) {'use strict'; + // Note that someSideEffect is not in the factory function parameter list + })`, + }, + }); + const {renderer, program} = setup(PROGRAM); + const file = + getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); + const output = new MagicString(PROGRAM.contents); + renderer.addImports( + output, + [ + {specifier: '@angular/core', qualifier: ts.createIdentifier('i0')}, + {specifier: '@angular/common', qualifier: ts.createIdentifier('i1')} + ], + file); + const outputSrc = output.toString(); - renderer.addImports(output, [], file); - const contentsAfter = output.toString(); + expect(outputSrc).toContain( + `typeof exports === 'object' && typeof module !== 'undefined' ? ` + + `factory(require('@angular/core'),require('@angular/common'),exports,require('/local-dep'),require('@angular/core'),require('some-side-effect')) :`); + expect(outputSrc).toContain( + `typeof define === 'function' && define.amd ? define('file', ` + + `['@angular/core','@angular/common','exports','/local-dep','@angular/core','some-side-effect'], factory) :`); + expect(outputSrc).toContain( + `(factory(global.ng.core,global.ng.common,global.file,global.localDep,global.ng.core,global.someSideEffect));`); + expect(outputSrc).toContain(`(function (i0,i1,exports,localDep,core) {'use strict';`); + }); + }); - expect(contentsAfter).toBe(contentsBefore); - }); + describe('addExports', () => { + it('should insert the given exports at the end of the source file', () => { + const {importManager, renderer, sourceFile} = setup(PROGRAM); + const output = new MagicString(PROGRAM.contents); + const generateNamedImportSpy = + spyOn(importManager, 'generateNamedImport').and.callThrough(); + renderer.addExports( + output, PROGRAM.name.replace(/\.js$/, ''), + [ + {from: _('/node_modules/test-package/some/a.js'), identifier: 'ComponentA1'}, + {from: _('/node_modules/test-package/some/a.js'), identifier: 'ComponentA2'}, + {from: _('/node_modules/test-package/some/foo/b.js'), identifier: 'ComponentB'}, + {from: PROGRAM.name, identifier: 'TopLevelComponent'}, + ], + importManager, sourceFile); - it('should handle the case where not all dependencies are used by the factory', () => { - const PROGRAM: TestFile = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` - /* A copyright notice */ - /* A copyright notice */ - (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports,require('/local-dep'),require('@angular/core'),require('some-side-effect')) : - typeof define === 'function' && define.amd ? define('file', ['exports','/local-dep','@angular/core','some-side-effect'], factory) : - (factory(global.file,global.localDep,global.ng.core,global.someSideEffect)); - }(this, (function (exports,localDep,core) {'use strict'; - // Note that someSideEffect is not in the factory function parameter list - })));`, - }; - const {renderer, program} = setup(PROGRAM); - const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); - const output = new MagicString(PROGRAM.contents); - renderer.addImports( - output, - [ - {specifier: '@angular/core', qualifier: ts.createIdentifier('i0')}, - {specifier: '@angular/common', qualifier: ts.createIdentifier('i1')} - ], - file); - const outputSrc = output.toString(); - - expect(outputSrc).toContain( - `typeof exports === 'object' && typeof module !== 'undefined' ? ` + - `factory(require('@angular/core'),require('@angular/common'),exports,require('/local-dep'),require('@angular/core'),require('some-side-effect')) :`); - expect(outputSrc).toContain( - `typeof define === 'function' && define.amd ? define('file', ` + - `['@angular/core','@angular/common','exports','/local-dep','@angular/core','some-side-effect'], factory) :`); - expect(outputSrc).toContain( - `(factory(global.ng.core,global.ng.common,global.file,global.localDep,global.ng.core,global.someSideEffect));`); - expect(outputSrc).toContain(`(function (i0,i1,exports,localDep,core) {'use strict';`); - }); - }); - - describe('addExports', () => { - it('should insert the given exports at the end of the source file', () => { - const {importManager, renderer, sourceFile} = setup(PROGRAM); - const output = new MagicString(PROGRAM.contents); - const generateNamedImportSpy = - spyOn(importManager, 'generateNamedImport').and.callThrough(); - renderer.addExports( - output, PROGRAM.name.replace(/\.js$/, ''), - [ - {from: _('/node_modules/test-package/some/a.js'), identifier: 'ComponentA1'}, - {from: _('/node_modules/test-package/some/a.js'), identifier: 'ComponentA2'}, - {from: _('/node_modules/test-package/some/foo/b.js'), identifier: 'ComponentB'}, - {from: PROGRAM.name, identifier: 'TopLevelComponent'}, - ], - importManager, sourceFile); - - expect(output.toString()).toContain(` + expect(output.toString()).toContain(` exports.A = A; exports.B = B; exports.C = C; @@ -407,284 +467,296 @@ exports.ComponentA1 = i0.ComponentA1; exports.ComponentA2 = i0.ComponentA2; exports.ComponentB = i1.ComponentB; exports.TopLevelComponent = TopLevelComponent; -})));`); +}))`); - expect(generateNamedImportSpy).toHaveBeenCalledWith('./a', 'ComponentA1'); - expect(generateNamedImportSpy).toHaveBeenCalledWith('./a', 'ComponentA2'); - expect(generateNamedImportSpy).toHaveBeenCalledWith('./foo/b', 'ComponentB'); - }); - }); + expect(generateNamedImportSpy).toHaveBeenCalledWith('./a', 'ComponentA1'); + expect(generateNamedImportSpy).toHaveBeenCalledWith('./a', 'ComponentA2'); + expect(generateNamedImportSpy).toHaveBeenCalledWith('./foo/b', 'ComponentB'); + }); + }); - describe('addConstants', () => { - it('should insert the given constants after imports in the source file', () => { - const {renderer, program} = setup(PROGRAM); - const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); - const output = new MagicString(PROGRAM.contents); - renderer.addConstants(output, 'var x = 3;', file); - expect(output.toString()).toContain(` -}(this, (function (exports,someSideEffect,localDep,core) { + describe('addConstants', () => { + it('should insert the given constants after imports in the source file', () => { + const {renderer, program} = setup(PROGRAM); + const file = + getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); + const output = new MagicString(PROGRAM.contents); + renderer.addConstants(output, 'var x = 3;', file); + expect(output.toString()) + .toContain(`(this, (function (exports,someSideEffect,localDep,core) { var x = 3; 'use strict'; var A = (function() {`); - }); + }); - it('should insert constants after inserted imports', - () => { - // This test (from ESM5) is not needed as constants go in the body - // of the UMD IIFE, so cannot come before imports. - }); - }); + it('should insert constants after inserted imports', + () => { + // This test (from ESM5) is not needed as constants go in the body + // of the UMD IIFE, so cannot come before imports. + }); + }); - describe('rewriteSwitchableDeclarations', () => { - it('should switch marked declaration initializers', () => { - const {renderer, program, sourceFile, switchMarkerAnalyses} = setup(PROGRAM); - const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); - const output = new MagicString(PROGRAM.contents); - renderer.rewriteSwitchableDeclarations( - output, file, switchMarkerAnalyses.get(sourceFile)!.declarations); - expect(output.toString()) - .not.toContain(`var compileNgModuleFactory = compileNgModuleFactory__PRE_R3__;`); - expect(output.toString()) - .toContain(`var badlyFormattedVariable = __PRE_R3__badlyFormattedVariable;`); - expect(output.toString()) - .toContain(`var compileNgModuleFactory = compileNgModuleFactory__POST_R3__;`); - expect(output.toString()) - .toContain( - `function compileNgModuleFactory__PRE_R3__(injector, options, moduleType) {`); - expect(output.toString()) - .toContain( - `function compileNgModuleFactory__POST_R3__(injector, options, moduleType) {`); - }); - }); + describe('rewriteSwitchableDeclarations', () => { + it('should switch marked declaration initializers', () => { + const {renderer, program, sourceFile, switchMarkerAnalyses} = setup(PROGRAM); + const file = + getSourceFileOrError(program, _('/node_modules/test-package/some/file.js')); + const output = new MagicString(PROGRAM.contents); + renderer.rewriteSwitchableDeclarations( + output, file, switchMarkerAnalyses.get(sourceFile)!.declarations); + expect(output.toString()) + .not.toContain(`var compileNgModuleFactory = compileNgModuleFactory__PRE_R3__;`); + expect(output.toString()) + .toContain(`var badlyFormattedVariable = __PRE_R3__badlyFormattedVariable;`); + expect(output.toString()) + .toContain(`var compileNgModuleFactory = compileNgModuleFactory__POST_R3__;`); + expect(output.toString()) + .toContain( + `function compileNgModuleFactory__PRE_R3__(injector, options, moduleType) {`); + expect(output.toString()) + .toContain( + `function compileNgModuleFactory__POST_R3__(injector, options, moduleType) {`); + }); + }); - describe('addDefinitions', () => { - it('should insert the definitions directly before the return statement of the class IIFE', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM); - const output = new MagicString(PROGRAM.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'A')!; - renderer.addDefinitions(output, compiledClass, 'SOME DEFINITION TEXT'); - expect(output.toString()).toContain(` + describe('addDefinitions', () => { + it('should insert the definitions directly before the return statement of the class IIFE', + () => { + const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM); + const output = new MagicString(PROGRAM.contents); + const compiledClass = + decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'A')!; + renderer.addDefinitions(output, compiledClass, 'SOME DEFINITION TEXT'); + expect(output.toString()).toContain(` A.prototype.ngDoCheck = function() { // }; SOME DEFINITION TEXT return A; `); - }); + }); - it('should error if the compiledClass is not valid', () => { - const {renderer, sourceFile, program} = setup(PROGRAM); - const output = new MagicString(PROGRAM.contents); + it('should error if the compiledClass is not valid', () => { + const {renderer, sourceFile, program} = setup(PROGRAM); + const output = new MagicString(PROGRAM.contents); - const noIifeDeclaration = getDeclaration( - program, absoluteFromSourceFile(sourceFile), 'NoIife', ts.isFunctionDeclaration); - const mockNoIifeClass: any = {declaration: noIifeDeclaration, name: 'NoIife'}; - expect(() => renderer.addDefinitions(output, mockNoIifeClass, 'SOME DEFINITION TEXT')) - .toThrowError( - `Compiled class "NoIife" in "${ - _('/node_modules/test-package/some/file.js')}" does not have a valid syntax.\n` + - `Expected an ES5 IIFE wrapped function. But got:\n` + - `function NoIife() {}`); + const noIifeDeclaration = getDeclaration( + program, absoluteFromSourceFile(sourceFile), 'NoIife', ts.isFunctionDeclaration); + const mockNoIifeClass: any = {declaration: noIifeDeclaration, name: 'NoIife'}; + expect(() => renderer.addDefinitions(output, mockNoIifeClass, 'SOME DEFINITION TEXT')) + .toThrowError( + `Compiled class "NoIife" in "${ + _('/node_modules/test-package/some/file.js')}" does not have a valid syntax.\n` + + `Expected an ES5 IIFE wrapped function. But got:\n` + + `function NoIife() {}`); - const badIifeDeclaration = getDeclaration( - program, absoluteFromSourceFile(sourceFile), 'BadIife', ts.isVariableDeclaration); - const mockBadIifeClass: any = {declaration: badIifeDeclaration, name: 'BadIife'}; - expect(() => renderer.addDefinitions(output, mockBadIifeClass, 'SOME DEFINITION TEXT')) - .toThrowError( - `Compiled class wrapper IIFE does not have a return statement: BadIife in ${ - _('/node_modules/test-package/some/file.js')}`); - }); - }); + const badIifeDeclaration = getDeclaration( + program, absoluteFromSourceFile(sourceFile), 'BadIife', ts.isVariableDeclaration); + const mockBadIifeClass: any = {declaration: badIifeDeclaration, name: 'BadIife'}; + expect(() => renderer.addDefinitions(output, mockBadIifeClass, 'SOME DEFINITION TEXT')) + .toThrowError( + `Compiled class wrapper IIFE does not have a return statement: BadIife in ${ + _('/node_modules/test-package/some/file.js')}`); + }); + }); - describe('addAdjacentStatements', () => { - const contents = `(function (global, factory) {\n` + - ` typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports,require('tslib'),require('@angular/core')) :\n` + - ` typeof define === 'function' && define.amd ? define('file', ['exports','/tslib','@angular/core'], factory) :\n` + - ` (factory(global.file,global.tslib,global.ng.core));\n` + - ` }(this, (function (exports,tslib,core) {'use strict';\n` + - `\n` + - ` var SomeDirective = /** @class **/ (function () {\n` + - ` function SomeDirective(zone, cons) {}\n` + - ` SomeDirective.prototype.method = function() {}\n` + - ` SomeDirective.decorators = [\n` + - ` { type: core.Directive, args: [{ selector: '[a]' }] },\n` + - ` { type: OtherA }\n` + - ` ];\n` + - ` SomeDirective.ctorParameters = function() { return [\n` + - ` { type: core.NgZone },\n` + - ` { type: core.Console }\n` + - ` ]; };\n` + - ` return SomeDirective;\n` + - ` }());\n` + - ` exports.SomeDirective = SomeDirective;\n` + - `})));`; - - it('should insert the statements after all the static methods of the class', () => { - const program = {name: _('/node_modules/test-package/some/file.js'), contents}; - const {renderer, decorationAnalyses, sourceFile} = setup(program); - const output = new MagicString(contents); - const compiledClass = decorationAnalyses.get(sourceFile)!.compiledClasses.find( - c => c.name === 'SomeDirective')!; - renderer.addAdjacentStatements(output, compiledClass, 'SOME STATEMENTS'); - expect(output.toString()) - .toContain( + describe('addAdjacentStatements', () => { + const contents: TestFileSpec['contents'] = { + wrapperFunction: `function (global, factory) {\n` + + ` typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports,require('tslib'),require('@angular/core')) :\n` + + ` typeof define === 'function' && define.amd ? define('file', ['exports','/tslib','@angular/core'], factory) :\n` + + ` (factory(global.file,global.tslib,global.ng.core));\n` + + ` }`, + wrapperCallArguments: `this, (function (exports,tslib,core) {'use strict';\n` + + `\n` + + ` var SomeDirective = /** @class **/ (function () {\n` + + ` function SomeDirective(zone, cons) {}\n` + + ` SomeDirective.prototype.method = function() {}\n` + + ` SomeDirective.decorators = [\n` + + ` { type: core.Directive, args: [{ selector: '[a]' }] },\n` + + ` { type: OtherA }\n` + + ` ];\n` + ` SomeDirective.ctorParameters = function() { return [\n` + ` { type: core.NgZone },\n` + ` { type: core.Console }\n` + ` ]; };\n` + - `SOME STATEMENTS\n` + - ` return SomeDirective;\n`); - }); + ` return SomeDirective;\n` + + ` }());\n` + + ` exports.SomeDirective = SomeDirective;\n` + + `})`, + }; - it('should insert the statements after any definitions', () => { - const program = {name: _('/node_modules/test-package/some/file.js'), contents}; - const {renderer, decorationAnalyses, sourceFile} = setup(program); - const output = new MagicString(contents); - const compiledClass = decorationAnalyses.get(sourceFile)!.compiledClasses.find( - c => c.name === 'SomeDirective')!; - renderer.addDefinitions(output, compiledClass, 'SOME DEFINITIONS'); - renderer.addAdjacentStatements(output, compiledClass, 'SOME STATEMENTS'); - const definitionsPosition = output.toString().indexOf('SOME DEFINITIONS'); - const statementsPosition = output.toString().indexOf('SOME STATEMENTS'); - expect(definitionsPosition).not.toEqual(-1, 'definitions should exist'); - expect(statementsPosition).not.toEqual(-1, 'statements should exist'); - expect(statementsPosition).toBeGreaterThan(definitionsPosition); - }); - }); + it('should insert the statements after all the static methods of the class', () => { + const program = + formatFactory({name: _('/node_modules/test-package/some/file.js'), contents}); + const {renderer, decorationAnalyses, sourceFile} = setup(program); + const output = new MagicString(program.contents); + const compiledClass = decorationAnalyses.get(sourceFile)!.compiledClasses.find( + c => c.name === 'SomeDirective')!; + renderer.addAdjacentStatements(output, compiledClass, 'SOME STATEMENTS'); + expect(output.toString()) + .toContain( + ` SomeDirective.ctorParameters = function() { return [\n` + + ` { type: core.NgZone },\n` + + ` { type: core.Console }\n` + + ` ]; };\n` + + `SOME STATEMENTS\n` + + ` return SomeDirective;\n`); + }); - describe('removeDecorators', () => { - it('should delete the decorator (and following comma) that was matched in the analysis', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM); - const output = new MagicString(PROGRAM.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'A')!; - const decorator = compiledClass.decorators![0]; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - expect(output.toString()) - .not.toContain(`{ type: core.Directive, args: [{ selector: '[a]' }] },`); - expect(output.toString()).toContain(`{ type: OtherA }`); - expect(output.toString()) - .toContain(`{ type: core.Directive, args: [{ selector: '[b]' }] }`); - expect(output.toString()).toContain(`{ type: OtherB }`); - expect(output.toString()) - .toContain(`{ type: core.Directive, args: [{ selector: '[c]' }] }`); - }); + it('should insert the statements after any definitions', () => { + const program = + formatFactory({name: _('/node_modules/test-package/some/file.js'), contents}); + const {renderer, decorationAnalyses, sourceFile} = setup(program); + const output = new MagicString(program.contents); + const compiledClass = decorationAnalyses.get(sourceFile)!.compiledClasses.find( + c => c.name === 'SomeDirective')!; + renderer.addDefinitions(output, compiledClass, 'SOME DEFINITIONS'); + renderer.addAdjacentStatements(output, compiledClass, 'SOME STATEMENTS'); + const definitionsPosition = output.toString().indexOf('SOME DEFINITIONS'); + const statementsPosition = output.toString().indexOf('SOME STATEMENTS'); + expect(definitionsPosition).not.toEqual(-1, 'definitions should exist'); + expect(statementsPosition).not.toEqual(-1, 'statements should exist'); + expect(statementsPosition).toBeGreaterThan(definitionsPosition); + }); + }); + + describe('removeDecorators', () => { + it('should delete the decorator (and following comma) that was matched in the analysis', + () => { + const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM); + const output = new MagicString(PROGRAM.contents); + const compiledClass = + decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'A')!; + const decorator = compiledClass.decorators![0]; + const decoratorsToRemove = new Map(); + decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); + renderer.removeDecorators(output, decoratorsToRemove); + expect(output.toString()) + .not.toContain(`{ type: core.Directive, args: [{ selector: '[a]' }] },`); + expect(output.toString()).toContain(`{ type: OtherA }`); + expect(output.toString()) + .toContain(`{ type: core.Directive, args: [{ selector: '[b]' }] }`); + expect(output.toString()).toContain(`{ type: OtherB }`); + expect(output.toString()) + .toContain(`{ type: core.Directive, args: [{ selector: '[c]' }] }`); + }); - it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM); - const output = new MagicString(PROGRAM.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'B')!; - const decorator = compiledClass.decorators![0]; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - expect(output.toString()) - .toContain(`{ type: core.Directive, args: [{ selector: '[a]' }] },`); - expect(output.toString()).toContain(`{ type: OtherA }`); - expect(output.toString()) - .not.toContain(`{ type: core.Directive, args: [{ selector: '[b]' }] }`); - expect(output.toString()).toContain(`{ type: OtherB }`); - expect(output.toString()) - .toContain(`{ type: core.Directive, args: [{ selector: '[c]' }] }`); - }); + it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis', + () => { + const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM); + const output = new MagicString(PROGRAM.contents); + const compiledClass = + decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'B')!; + const decorator = compiledClass.decorators![0]; + const decoratorsToRemove = new Map(); + decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); + renderer.removeDecorators(output, decoratorsToRemove); + expect(output.toString()) + .toContain(`{ type: core.Directive, args: [{ selector: '[a]' }] },`); + expect(output.toString()).toContain(`{ type: OtherA }`); + expect(output.toString()) + .not.toContain(`{ type: core.Directive, args: [{ selector: '[b]' }] }`); + expect(output.toString()).toContain(`{ type: OtherB }`); + expect(output.toString()) + .toContain(`{ type: core.Directive, args: [{ selector: '[c]' }] }`); + }); - it('should delete the decorator (and its container if there are not other decorators left) that was matched in the analysis', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM); - const output = new MagicString(PROGRAM.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'C')!; - const decorator = compiledClass.decorators![0]; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - renderer.addDefinitions(output, compiledClass, 'SOME DEFINITION TEXT'); - expect(output.toString()) - .toContain(`{ type: core.Directive, args: [{ selector: '[a]' }] },`); - expect(output.toString()).toContain(`{ type: OtherA }`); - expect(output.toString()) - .toContain(`{ type: core.Directive, args: [{ selector: '[b]' }] }`); - expect(output.toString()).toContain(`{ type: OtherB }`); - expect(output.toString()).not.toContain(`C.decorators`); - }); - }); + it('should delete the decorator (and its container if there are not other decorators left) that was matched in the analysis', + () => { + const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM); + const output = new MagicString(PROGRAM.contents); + const compiledClass = + decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'C')!; + const decorator = compiledClass.decorators![0]; + const decoratorsToRemove = new Map(); + decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); + renderer.removeDecorators(output, decoratorsToRemove); + renderer.addDefinitions(output, compiledClass, 'SOME DEFINITION TEXT'); + expect(output.toString()) + .toContain(`{ type: core.Directive, args: [{ selector: '[a]' }] },`); + expect(output.toString()).toContain(`{ type: OtherA }`); + expect(output.toString()) + .toContain(`{ type: core.Directive, args: [{ selector: '[b]' }] }`); + expect(output.toString()).toContain(`{ type: OtherB }`); + expect(output.toString()).not.toContain(`C.decorators`); + }); + }); - describe('[__decorate declarations]', () => { - it('should delete the decorator (and following comma) that was matched in the analysis', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER); - const output = new MagicString(PROGRAM_DECORATE_HELPER.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'A')!; - const decorator = compiledClass.decorators!.find(d => d.name === 'Directive')!; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - expect(output.toString()).not.toContain(`core.Directive({ selector: '[a]' }),`); - expect(output.toString()).toContain(`OtherA()`); - expect(output.toString()).toContain(`core.Directive({ selector: '[b]' })`); - expect(output.toString()).toContain(`OtherB()`); - expect(output.toString()).toContain(`core.Directive({ selector: '[c]' })`); - }); + describe('[__decorate declarations]', () => { + it('should delete the decorator (and following comma) that was matched in the analysis', + () => { + const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER); + const output = new MagicString(PROGRAM_DECORATE_HELPER.contents); + const compiledClass = + decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'A')!; + const decorator = compiledClass.decorators!.find(d => d.name === 'Directive')!; + const decoratorsToRemove = new Map(); + decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); + renderer.removeDecorators(output, decoratorsToRemove); + expect(output.toString()).not.toContain(`core.Directive({ selector: '[a]' }),`); + expect(output.toString()).toContain(`OtherA()`); + expect(output.toString()).toContain(`core.Directive({ selector: '[b]' })`); + expect(output.toString()).toContain(`OtherB()`); + expect(output.toString()).toContain(`core.Directive({ selector: '[c]' })`); + }); - it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER); - const output = new MagicString(PROGRAM_DECORATE_HELPER.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'B')!; - const decorator = compiledClass.decorators!.find(d => d.name === 'Directive')!; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - expect(output.toString()).toContain(`core.Directive({ selector: '[a]' }),`); - expect(output.toString()).toContain(`OtherA()`); - expect(output.toString()).not.toContain(`core.Directive({ selector: '[b]' })`); - expect(output.toString()).toContain(`OtherB()`); - expect(output.toString()).toContain(`core.Directive({ selector: '[c]' })`); - }); + it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis', + () => { + const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER); + const output = new MagicString(PROGRAM_DECORATE_HELPER.contents); + const compiledClass = + decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'B')!; + const decorator = compiledClass.decorators!.find(d => d.name === 'Directive')!; + const decoratorsToRemove = new Map(); + decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); + renderer.removeDecorators(output, decoratorsToRemove); + expect(output.toString()).toContain(`core.Directive({ selector: '[a]' }),`); + expect(output.toString()).toContain(`OtherA()`); + expect(output.toString()).not.toContain(`core.Directive({ selector: '[b]' })`); + expect(output.toString()).toContain(`OtherB()`); + expect(output.toString()).toContain(`core.Directive({ selector: '[c]' })`); + }); - it('should delete the decorator (and its container if there are no other decorators left) that was matched in the analysis', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER); - const output = new MagicString(PROGRAM_DECORATE_HELPER.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'C')!; - const decorator = compiledClass.decorators!.find(d => d.name === 'Directive')!; - const decoratorsToRemove = new Map(); - decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); - renderer.removeDecorators(output, decoratorsToRemove); - expect(output.toString()).toContain(`core.Directive({ selector: '[a]' }),`); - expect(output.toString()).toContain(`OtherA()`); - expect(output.toString()).toContain(`core.Directive({ selector: '[b]' })`); - expect(output.toString()).toContain(`OtherB()`); - expect(output.toString()).not.toContain(`core.Directive({ selector: '[c]' })`); - expect(output.toString()).not.toContain(`C = tslib_1.__decorate([`); - expect(output.toString()).toContain(`function C() {\n }\n return C;`); - }); - }); + it('should delete the decorator (and its container if there are no other decorators left) that was matched in the analysis', + () => { + const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER); + const output = new MagicString(PROGRAM_DECORATE_HELPER.contents); + const compiledClass = + decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'C')!; + const decorator = compiledClass.decorators!.find(d => d.name === 'Directive')!; + const decoratorsToRemove = new Map(); + decoratorsToRemove.set(decorator.node!.parent!, [decorator.node!]); + renderer.removeDecorators(output, decoratorsToRemove); + expect(output.toString()).toContain(`core.Directive({ selector: '[a]' }),`); + expect(output.toString()).toContain(`OtherA()`); + expect(output.toString()).toContain(`core.Directive({ selector: '[b]' })`); + expect(output.toString()).toContain(`OtherB()`); + expect(output.toString()).not.toContain(`core.Directive({ selector: '[c]' })`); + expect(output.toString()).not.toContain(`C = tslib_1.__decorate([`); + expect(output.toString()).toContain(`function C() {\n }\n return C;`); + }); + }); - describe('printStatement', () => { - it('should transpile code to ES5', () => { - const {renderer, sourceFile, importManager} = setup(PROGRAM); + describe('printStatement', () => { + it('should transpile code to ES5', () => { + const {renderer, sourceFile, importManager} = setup(PROGRAM); - const stmt1 = new DeclareVarStmt('foo', new LiteralExpr(42), null, [StmtModifier.Static]); - const stmt2 = new DeclareVarStmt('bar', new LiteralExpr(true)); - const stmt3 = new DeclareVarStmt('baz', new LiteralExpr('qux'), undefined, []); + const stmt1 = + new DeclareVarStmt('foo', new LiteralExpr(42), null, [StmtModifier.Static]); + const stmt2 = new DeclareVarStmt('bar', new LiteralExpr(true)); + const stmt3 = new DeclareVarStmt('baz', new LiteralExpr('qux'), undefined, []); - expect(renderer.printStatement(stmt1, sourceFile, importManager)).toBe('var foo = 42;'); - expect(renderer.printStatement(stmt2, sourceFile, importManager)).toBe('var bar = true;'); - expect(renderer.printStatement(stmt3, sourceFile, importManager)).toBe('var baz = "qux";'); + expect(renderer.printStatement(stmt1, sourceFile, importManager)).toBe('var foo = 42;'); + expect(renderer.printStatement(stmt2, sourceFile, importManager)) + .toBe('var bar = true;'); + expect(renderer.printStatement(stmt3, sourceFile, importManager)) + .toBe('var baz = "qux";'); + }); + }); }); }); });