From c8ee390d2395e1f3194c234ea7b4970daaaa0511 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Tue, 12 May 2020 08:20:00 +0100 Subject: [PATCH] fix(ngcc): ensure rendering formatters work with IIFE wrapped classes (#36989) After the refactoring of the reflection hosts to accommodate ES2015 classes wrapped in IIFEs. The same treatment needs to be applied to the rendering formatters. PR Close #36989 --- .../ngcc/src/host/esm2015_host.ts | 2 +- .../src/rendering/esm5_rendering_formatter.ts | 23 +- .../src/rendering/esm_rendering_formatter.ts | 16 +- .../commonjs_rendering_formatter_spec.ts | 7 +- .../esm5_rendering_formatter_spec.ts | 7 +- .../rendering/esm_rendering_formatter_spec.ts | 816 +++++++++--------- .../rendering/umd_rendering_formatter_spec.ts | 7 +- 7 files changed, 469 insertions(+), 409 deletions(-) diff --git a/packages/compiler-cli/ngcc/src/host/esm2015_host.ts b/packages/compiler-cli/ngcc/src/host/esm2015_host.ts index 1936783d1d..da1f8d8034 100644 --- a/packages/compiler-cli/ngcc/src/host/esm2015_host.ts +++ b/packages/compiler-cli/ngcc/src/host/esm2015_host.ts @@ -2489,7 +2489,7 @@ function isSynthesizedSuperCall(expression: ts.Expression): boolean { * Find the statement that contains the given node * @param node a node whose containing statement we wish to find */ -function getContainingStatement(node: ts.Node): ts.Statement { +export function getContainingStatement(node: ts.Node): ts.Statement { while (node.parent) { if (ts.isBlock(node.parent) || ts.isSourceFile(node.parent)) { break; diff --git a/packages/compiler-cli/ngcc/src/rendering/esm5_rendering_formatter.ts b/packages/compiler-cli/ngcc/src/rendering/esm5_rendering_formatter.ts index 40a3474578..a049539531 100644 --- a/packages/compiler-cli/ngcc/src/rendering/esm5_rendering_formatter.ts +++ b/packages/compiler-cli/ngcc/src/rendering/esm5_rendering_formatter.ts @@ -8,10 +8,12 @@ import {Statement} from '@angular/compiler'; import MagicString from 'magic-string'; import * as ts from 'typescript'; + import {NOOP_DEFAULT_IMPORT_RECORDER} from '../../../src/ngtsc/imports'; import {ImportManager, translateStatement} from '../../../src/ngtsc/translator'; import {CompiledClass} from '../analysis/types'; -import {getIifeBody} from '../host/esm5_host'; +import {getContainingStatement} from '../host/esm2015_host'; + import {EsmRenderingFormatter} from './esm_rendering_formatter'; /** @@ -20,11 +22,24 @@ import {EsmRenderingFormatter} from './esm_rendering_formatter'; */ export class Esm5RenderingFormatter extends EsmRenderingFormatter { /** - * Add the definitions inside the IIFE of each decorated class + * Add the definitions, directly before the return statement, inside the IIFE of each decorated + * class. */ addDefinitions(output: MagicString, compiledClass: CompiledClass, definitions: string): void { - const iifeBody = getIifeBody(compiledClass.declaration); - if (!iifeBody) { + const classSymbol = this.host.getClassSymbol(compiledClass.declaration); + if (!classSymbol) { + throw new Error( + `Compiled class "${compiledClass.name}" in "${ + compiledClass.declaration.getSourceFile() + .fileName}" does not have a valid syntax.\n` + + `Expected an ES5 IIFE wrapped function. But got:\n` + + compiledClass.declaration.getText()); + } + const declarationStatement = + getContainingStatement(classSymbol.implementation.valueDeclaration); + + const iifeBody = declarationStatement.parent; + if (!iifeBody || !ts.isBlock(iifeBody)) { throw new Error(`Compiled class declaration is not inside an IIFE: ${compiledClass.name} in ${ compiledClass.declaration.getSourceFile().fileName}`); } diff --git a/packages/compiler-cli/ngcc/src/rendering/esm_rendering_formatter.ts b/packages/compiler-cli/ngcc/src/rendering/esm_rendering_formatter.ts index 81739a50c1..45ff7aaf5a 100644 --- a/packages/compiler-cli/ngcc/src/rendering/esm_rendering_formatter.ts +++ b/packages/compiler-cli/ngcc/src/rendering/esm_rendering_formatter.ts @@ -16,7 +16,7 @@ import {isDtsPath} from '../../../src/ngtsc/util/src/typescript'; import {ModuleWithProvidersInfo} from '../analysis/module_with_providers_analyzer'; import {ExportInfo} from '../analysis/private_declarations_analyzer'; import {CompiledClass} from '../analysis/types'; -import {isAssignment} from '../host/esm2015_host'; +import {getContainingStatement, isAssignment} from '../host/esm2015_host'; import {NgccReflectionHost, POST_R3_MARKER, PRE_R3_MARKER, SwitchableVariableDeclaration} from '../host/ngcc_host'; import {RedundantDecoratorMap, RenderingFormatter} from './rendering_formatter'; @@ -104,7 +104,8 @@ export class EsmRenderingFormatter implements RenderingFormatter { if (!classSymbol) { throw new Error(`Compiled class does not have a valid symbol: ${compiledClass.name}`); } - const declarationStatement = getDeclarationStatement(classSymbol.declaration.valueDeclaration); + const declarationStatement = + getContainingStatement(classSymbol.implementation.valueDeclaration); const insertionPoint = declarationStatement.getEnd(); output.appendLeft(insertionPoint, '\n' + definitions); } @@ -277,17 +278,6 @@ export class EsmRenderingFormatter implements RenderingFormatter { } } -function getDeclarationStatement(node: ts.Node): ts.Statement { - let statement = node; - while (statement) { - if (ts.isVariableStatement(statement) || ts.isClassDeclaration(statement)) { - return statement; - } - statement = statement.parent; - } - throw new Error(`Class is not defined in a declaration statement: ${node.getText()}`); -} - function findStatement(node: ts.Node): ts.Statement|undefined { while (node) { if (ts.isExpressionStatement(node) || ts.isReturnStatement(node)) { diff --git a/packages/compiler-cli/ngcc/test/rendering/commonjs_rendering_formatter_spec.ts b/packages/compiler-cli/ngcc/test/rendering/commonjs_rendering_formatter_spec.ts index 914208bedc..d61541ac22 100644 --- a/packages/compiler-cli/ngcc/test/rendering/commonjs_rendering_formatter_spec.ts +++ b/packages/compiler-cli/ngcc/test/rendering/commonjs_rendering_formatter_spec.ts @@ -315,8 +315,11 @@ SOME DEFINITION TEXT program, absoluteFromSourceFile(sourceFile), 'NoIife', ts.isFunctionDeclaration); const mockNoIifeClass: any = {declaration: noIifeDeclaration, name: 'NoIife'}; expect(() => renderer.addDefinitions(output, mockNoIifeClass, 'SOME DEFINITION TEXT')) - .toThrowError(`Compiled class declaration is not inside an IIFE: NoIife in ${ - _('/node_modules/test-package/some/file.js')}`); + .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); diff --git a/packages/compiler-cli/ngcc/test/rendering/esm5_rendering_formatter_spec.ts b/packages/compiler-cli/ngcc/test/rendering/esm5_rendering_formatter_spec.ts index 72edb0a840..7472fb2e7d 100644 --- a/packages/compiler-cli/ngcc/test/rendering/esm5_rendering_formatter_spec.ts +++ b/packages/compiler-cli/ngcc/test/rendering/esm5_rendering_formatter_spec.ts @@ -321,8 +321,11 @@ SOME DEFINITION TEXT program, absoluteFromSourceFile(sourceFile), 'NoIife', ts.isFunctionDeclaration); const mockNoIifeClass: any = {declaration: noIifeDeclaration, name: 'NoIife'}; expect(() => renderer.addDefinitions(output, mockNoIifeClass, 'SOME DEFINITION TEXT')) - .toThrowError(`Compiled class declaration is not inside an IIFE: NoIife in ${ - _('/node_modules/test-package/some/file.js')}`); + .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); diff --git a/packages/compiler-cli/ngcc/test/rendering/esm_rendering_formatter_spec.ts b/packages/compiler-cli/ngcc/test/rendering/esm_rendering_formatter_spec.ts index d603411e60..97dac5d065 100644 --- a/packages/compiler-cli/ngcc/test/rendering/esm_rendering_formatter_spec.ts +++ b/packages/compiler-cli/ngcc/test/rendering/esm_rendering_formatter_spec.ts @@ -54,17 +54,8 @@ function setup(files: TestFile[], dtsFiles?: TestFile[]) { }; } -runInEachFileSystem(() => { - describe('EsmRenderingFormatter', () => { - let _: typeof absoluteFrom; - let PROGRAM: TestFile; - - beforeEach(() => { - _ = absoluteFrom; - - PROGRAM = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` +const PROGRAM_CONTENT: Record = { + 'top-level': ` /* A copyright notice */ import 'some-side-effect'; import {Directive} from '@angular/core'; @@ -97,245 +88,298 @@ function compileNgModuleFactory__POST_R3__(injector, options, moduleType) { ngDevMode && assertNgModuleType(moduleType); return Promise.resolve(new R3NgModuleFactory(moduleType)); } -// Some other content` - }; - }); +// Some other content`, - describe('addImports', () => { - it('should insert the given imports after existing imports of the source file', () => { - const {renderer, sourceFile} = setup([PROGRAM]); - const output = new MagicString(PROGRAM.contents); - renderer.addImports( - output, - [ - {specifier: '@angular/core', qualifier: 'i0'}, - {specifier: '@angular/common', qualifier: 'i1'} - ], - sourceFile); - expect(output.toString()).toContain(`/* A copyright notice */ + + 'iife-wrapped': ` +/* A copyright notice */ +import 'some-side-effect'; +import {Directive} from '@angular/core'; +let A = /** @class */ (() => { +class A {} +A.decorators = [ + { type: Directive, args: [{ selector: '[a]' }] }, + { type: OtherA } +]; +return A; +})(); +let B = /** @class */ (() => { +class B {} +B.decorators = [ + { type: OtherB }, + { type: Directive, args: [{ selector: '[b]' }] } +]; +return B; +})(); +var C_1; +let C = C_1 = /** @class */ (() => { +class C {} +C.decorators = [ + { type: Directive, args: [{ selector: '[c]' }] }, +]; +return C; +})(); +export A, B, C; +let compileNgModuleFactory = compileNgModuleFactory__PRE_R3__; +let badlyFormattedVariable = __PRE_R3__badlyFormattedVariable; +function compileNgModuleFactory__PRE_R3__(injector, options, moduleType) { + const compilerFactory = injector.get(CompilerFactory); + const compiler = compilerFactory.createCompiler([options]); + return compiler.compileModuleAsync(moduleType); +} +function compileNgModuleFactory__POST_R3__(injector, options, moduleType) { + ngDevMode && assertNgModuleType(moduleType); + return Promise.resolve(new R3NgModuleFactory(moduleType)); +} +// Some other content` +}; + +runInEachFileSystem(() => { + ['top-level', 'iife-wrapped'].forEach(classLayout => { + describe(`EsmRenderingFormatter {${classLayout} classes}`, () => { + let _: typeof absoluteFrom; + let PROGRAM: TestFile; + + beforeEach(() => { + _ = absoluteFrom; + + PROGRAM = { + name: _('/node_modules/test-package/some/file.js'), + contents: PROGRAM_CONTENT[classLayout], + }; + }); + + describe('addImports', () => { + it('should insert the given imports after existing imports of the source file', () => { + const {renderer, sourceFile} = setup([PROGRAM]); + const output = new MagicString(PROGRAM.contents); + renderer.addImports( + output, + [ + {specifier: '@angular/core', qualifier: 'i0'}, + {specifier: '@angular/common', qualifier: 'i1'} + ], + sourceFile); + expect(output.toString()).toContain(`/* A copyright notice */ import 'some-side-effect'; import {Directive} from '@angular/core'; import * as i0 from '@angular/core'; import * as i1 from '@angular/common';`); + }); + + it('should leave the file unchanged if there are no imports to add', () => { + const {renderer, sourceFile} = setup([PROGRAM]); + const output = new MagicString(PROGRAM.contents); + const contentsBefore = output.toString(); + + renderer.addImports(output, [], sourceFile); + const contentsAfter = output.toString(); + + expect(contentsAfter).toBe(contentsBefore); + }); }); - it('should leave the file unchanged if there are no imports to add', () => { - const {renderer, sourceFile} = setup([PROGRAM]); - const output = new MagicString(PROGRAM.contents); - const contentsBefore = output.toString(); - - renderer.addImports(output, [], sourceFile); - const contentsAfter = output.toString(); - - 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); - renderer.addExports( - output, _(PROGRAM.name.replace(/\.js$/, '')), - [ - { - from: _('/node_modules/test-package/some/a.js'), - dtsFrom: _('/node_modules/test-package/some/a.d.ts'), - identifier: 'ComponentA1' - }, - { - from: _('/node_modules/test-package/some/a.js'), - dtsFrom: _('/node_modules/test-package/some/a.d.ts'), - identifier: 'ComponentA2' - }, - { - from: _('/node_modules/test-package/some/foo/b.js'), - dtsFrom: _('/node_modules/test-package/some/foo/b.d.ts'), - identifier: 'ComponentB' - }, - {from: PROGRAM.name, dtsFrom: PROGRAM.name, identifier: 'TopLevelComponent'}, - ], - importManager, sourceFile); - expect(output.toString()).toContain(` + 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); + renderer.addExports( + output, _(PROGRAM.name.replace(/\.js$/, '')), + [ + { + from: _('/node_modules/test-package/some/a.js'), + dtsFrom: _('/node_modules/test-package/some/a.d.ts'), + identifier: 'ComponentA1' + }, + { + from: _('/node_modules/test-package/some/a.js'), + dtsFrom: _('/node_modules/test-package/some/a.d.ts'), + identifier: 'ComponentA2' + }, + { + from: _('/node_modules/test-package/some/foo/b.js'), + dtsFrom: _('/node_modules/test-package/some/foo/b.d.ts'), + identifier: 'ComponentB' + }, + {from: PROGRAM.name, dtsFrom: PROGRAM.name, identifier: 'TopLevelComponent'}, + ], + importManager, sourceFile); + expect(output.toString()).toContain(` // Some other content export {ComponentA1} from './a'; export {ComponentA2} from './a'; export {ComponentB} from './foo/b'; export {TopLevelComponent};`); + }); }); - }); - 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, 'const x = 3;', file); - expect(output.toString()).toContain(` + 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, 'const x = 3;', file); + expect(output.toString()).toContain(` import {Directive} from '@angular/core'; const x = 3; -export class A {}`); - }); +`); + }); - it('should insert constants after inserted imports', () => { - 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, 'const x = 3;', file); - renderer.addImports(output, [{specifier: '@angular/core', qualifier: 'i0'}], file); - expect(output.toString()).toContain(` + it('should insert constants after inserted imports', () => { + 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, 'const x = 3;', file); + renderer.addImports(output, [{specifier: '@angular/core', qualifier: 'i0'}], file); + expect(output.toString()).toContain(` import {Directive} from '@angular/core'; import * as i0 from '@angular/core'; const x = 3; -export class A {`); +`); + }); }); - }); - describe('rewriteSwitchableDeclarations', () => { - it('should switch marked declaration initializers', () => { - const {renderer, program, switchMarkerAnalyses, sourceFile} = 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(`let compileNgModuleFactory = compileNgModuleFactory__PRE_R3__;`); - expect(output.toString()) - .toContain(`let badlyFormattedVariable = __PRE_R3__badlyFormattedVariable;`); - expect(output.toString()) - .toContain(`let 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, switchMarkerAnalyses, sourceFile} = 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(`let compileNgModuleFactory = compileNgModuleFactory__PRE_R3__;`); + expect(output.toString()) + .toContain(`let badlyFormattedVariable = __PRE_R3__badlyFormattedVariable;`); + expect(output.toString()) + .toContain(`let 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 after the class declaration', () => { - 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(` -export class A {} + describe('addDefinitions', () => { + it('should insert the definitions directly after the class declaration', () => { + 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(`class A {} SOME DEFINITION TEXT A.decorators = [ `); - }); + }); - it('should insert the definitions after the variable declaration of class expressions', - () => { - const {renderer, decorationAnalyses, sourceFile} = setup([PROGRAM]); - const output = new MagicString(PROGRAM.contents); - const compiledClass = - decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'C')!; - renderer.addDefinitions(output, compiledClass, 'SOME DEFINITION TEXT'); - expect(output.toString()).toContain(` -let C = C_1 = class C {}; -SOME DEFINITION TEXT -C.decorators = [ -`); - }); - }); - - describe('addAdjacentStatements', () => { - const contents = `import {Directive, NgZone, Console} from '@angular/core';\n` + - `export class SomeDirective {\n` + - ` constructor(zone, cons) {}\n` + - ` method() {}\n` + - `}\n` + - `SomeDirective.decorators = [\n` + - ` { type: Directive, args: [{ selector: '[a]' }] },\n` + - ` { type: OtherA }\n` + - `];\n` + - `SomeDirective.ctorParameters = () => [\n` + - ` { type: NgZone },\n` + - ` { type: Console }\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( - `SomeDirective.ctorParameters = () => [\n` + - ` { type: NgZone },\n` + - ` { type: Console }\n` + - `];\n` + - `SOME STATEMENTS`); - }); - - 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); - }); - }); - - describe('removeDecorators', () => { - describe('[static property declaration]', () => { - it('should delete the decorator (and following comma) that was matched in the analysis', + it('should insert the definitions after the variable declaration of class expressions', () => { - const {decorationAnalyses, sourceFile, renderer} = setup([PROGRAM]); + 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); + decorationAnalyses.get(sourceFile)!.compiledClasses.find(c => c.name === 'C')!; + renderer.addDefinitions(output, compiledClass, 'SOME DEFINITION TEXT'); expect(output.toString()) - .not.toContain(`{ type: Directive, args: [{ selector: '[a]' }] },`); - expect(output.toString()).toContain(`{ type: OtherA }`); - expect(output.toString()) - .toContain(`{ type: Directive, args: [{ selector: '[b]' }] }`); - expect(output.toString()).toContain(`{ type: OtherB }`); - expect(output.toString()) - .toContain(`{ type: Directive, args: [{ selector: '[c]' }] }`); + .toMatch(/class C \{\};?\nSOME DEFINITION TEXT\nC.decorators = \[/); }); + }); + + describe('addAdjacentStatements', () => { + const contents = `import {Directive, NgZone, Console} from '@angular/core';\n` + + `export class SomeDirective {\n` + + ` constructor(zone, cons) {}\n` + + ` method() {}\n` + + `}\n` + + `SomeDirective.decorators = [\n` + + ` { type: Directive, args: [{ selector: '[a]' }] },\n` + + ` { type: OtherA }\n` + + `];\n` + + `SomeDirective.ctorParameters = () => [\n` + + ` { type: NgZone },\n` + + ` { type: Console }\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( + `SomeDirective.ctorParameters = () => [\n` + + ` { type: NgZone },\n` + + ` { type: Console }\n` + + `];\n` + + `SOME STATEMENTS`); + }); + + 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); + }); + }); + + describe('removeDecorators', () => { + describe('[static property declaration]', () => { + it('should delete the decorator (and following comma) that was matched in the analysis', + () => { + const {decorationAnalyses, sourceFile, renderer} = 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: Directive, args: [{ selector: '[a]' }] },`); + expect(output.toString()).toContain(`{ type: OtherA }`); + expect(output.toString()) + .toContain(`{ type: Directive, args: [{ selector: '[b]' }] }`); + expect(output.toString()).toContain(`{ type: OtherB }`); + expect(output.toString()) + .toContain(`{ type: Directive, args: [{ selector: '[c]' }] }`); + }); - it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis', - () => { - const {decorationAnalyses, sourceFile, renderer} = 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: Directive, args: [{ selector: '[a]' }] },`); - expect(output.toString()).toContain(`{ type: OtherA }`); - expect(output.toString()) - .not.toContain(`{ type: Directive, args: [{ selector: '[b]' }] }`); - expect(output.toString()).toContain(`{ type: OtherB }`); - expect(output.toString()) - .toContain(`{ type: Directive, args: [{ selector: '[c]' }] }`); - }); + it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis', + () => { + const {decorationAnalyses, sourceFile, renderer} = 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: Directive, args: [{ selector: '[a]' }] },`); + expect(output.toString()).toContain(`{ type: OtherA }`); + expect(output.toString()) + .not.toContain(`{ type: Directive, args: [{ selector: '[b]' }] }`); + expect(output.toString()).toContain(`{ type: OtherB }`); + expect(output.toString()) + .toContain(`{ type: Directive, args: [{ selector: '[c]' }] }`); + }); - it('should handle a decorator with a trailing comment', () => { - const text = ` + it('should handle a decorator with a trailing comment', () => { + const text = ` import {Directive} from '@angular/core'; export class A {} A.decorators = [ @@ -343,50 +387,50 @@ A.decorators = [ { type: OtherA } ]; `; - const file = {name: _('/node_modules/test-package/index.js'), contents: text}; - const {decorationAnalyses, sourceFile, renderer} = setup([file]); - const output = new MagicString(text); - 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); - // The decorator should have been removed correctly. - expect(output.toString()).toContain('A.decorators = [ { type: OtherA }'); + const file = {name: _('/node_modules/test-package/index.js'), contents: text}; + const {decorationAnalyses, sourceFile, renderer} = setup([file]); + const output = new MagicString(text); + 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); + // The decorator should have been removed correctly. + expect(output.toString()).toContain('A.decorators = [ { type: OtherA }'); + }); + + + it('should delete the decorator (and its container if there are no other decorators left) that was matched in the analysis', + () => { + const {decorationAnalyses, sourceFile, renderer} = 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); + expect(output.toString()) + .toContain(`{ type: Directive, args: [{ selector: '[a]' }] },`); + expect(output.toString()).toContain(`{ type: OtherA }`); + expect(output.toString()) + .toContain(`{ type: Directive, args: [{ selector: '[b]' }] }`); + expect(output.toString()).toContain(`{ type: OtherB }`); + expect(output.toString()) + .not.toContain(`{ type: Directive, args: [{ selector: '[c]' }] }`); + expect(output.toString()).not.toContain(`C.decorators = [`); + }); }); - - - it('should delete the decorator (and its container if there are no other decorators left) that was matched in the analysis', - () => { - const {decorationAnalyses, sourceFile, renderer} = 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); - expect(output.toString()) - .toContain(`{ type: Directive, args: [{ selector: '[a]' }] },`); - expect(output.toString()).toContain(`{ type: OtherA }`); - expect(output.toString()) - .toContain(`{ type: Directive, args: [{ selector: '[b]' }] }`); - expect(output.toString()).toContain(`{ type: OtherB }`); - expect(output.toString()) - .not.toContain(`{ type: Directive, args: [{ selector: '[c]' }] }`); - expect(output.toString()).not.toContain(`C.decorators = [`); - }); }); - }); - describe('[__decorate declarations]', () => { - let PROGRAM_DECORATE_HELPER: TestFile; + describe('[__decorate declarations]', () => { + let PROGRAM_DECORATE_HELPER: TestFile; - beforeEach(() => { - PROGRAM_DECORATE_HELPER = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` + beforeEach(() => { + PROGRAM_DECORATE_HELPER = { + name: _('/node_modules/test-package/some/file.js'), + contents: ` import * as tslib_1 from "tslib"; var D_1; /* A copyright notice */ @@ -420,72 +464,72 @@ D = D_1 = tslib_1.__decorate([ ], D); export { D }; // Some other content` - }; + }; + }); + + 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(`Directive({ selector: '[a]' }),`); + expect(output.toString()).toContain(`OtherA()`); + expect(output.toString()).toContain(`Directive({ selector: '[b]' })`); + expect(output.toString()).toContain(`OtherB()`); + expect(output.toString()).toContain(`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(`Directive({ selector: '[a]' }),`); + expect(output.toString()).toContain(`OtherA()`); + expect(output.toString()).not.toContain(`Directive({ selector: '[b]' })`); + expect(output.toString()).toContain(`OtherB()`); + expect(output.toString()).toContain(`Directive({ 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_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(`Directive({ selector: '[a]' }),`); + expect(output.toString()).toContain(`OtherA()`); + expect(output.toString()).toContain(`Directive({ selector: '[b]' })`); + expect(output.toString()).toContain(`OtherB()`); + expect(output.toString()).not.toContain(`Directive({ selector: '[c]' })`); + expect(output.toString()).not.toContain(`C = tslib_1.__decorate([`); + expect(output.toString()).toContain(`let C = class C {\n};\nexport { C };`); + }); }); - 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(`Directive({ selector: '[a]' }),`); - expect(output.toString()).toContain(`OtherA()`); - expect(output.toString()).toContain(`Directive({ selector: '[b]' })`); - expect(output.toString()).toContain(`OtherB()`); - expect(output.toString()).toContain(`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(`Directive({ selector: '[a]' }),`); - expect(output.toString()).toContain(`OtherA()`); - expect(output.toString()).not.toContain(`Directive({ selector: '[b]' })`); - expect(output.toString()).toContain(`OtherB()`); - expect(output.toString()).toContain(`Directive({ 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_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(`Directive({ selector: '[a]' }),`); - expect(output.toString()).toContain(`OtherA()`); - expect(output.toString()).toContain(`Directive({ selector: '[b]' })`); - expect(output.toString()).toContain(`OtherB()`); - expect(output.toString()).not.toContain(`Directive({ selector: '[c]' })`); - expect(output.toString()).not.toContain(`C = tslib_1.__decorate([`); - expect(output.toString()).toContain(`let C = class C {\n};\nexport { C };`); - }); - }); - - describe('addModuleWithProvidersParams', () => { - let MODULE_WITH_PROVIDERS_PROGRAM: TestFile[]; - let MODULE_WITH_PROVIDERS_DTS_PROGRAM: TestFile[]; - beforeEach(() => { - MODULE_WITH_PROVIDERS_PROGRAM = [ - { - name: _('/node_modules/test-package/src/index.js'), - contents: ` + describe('addModuleWithProvidersParams', () => { + let MODULE_WITH_PROVIDERS_PROGRAM: TestFile[]; + let MODULE_WITH_PROVIDERS_DTS_PROGRAM: TestFile[]; + beforeEach(() => { + MODULE_WITH_PROVIDERS_PROGRAM = [ + { + name: _('/node_modules/test-package/src/index.js'), + contents: ` import {ExternalModule} from './module'; import {LibraryModule} from 'some-library'; export class SomeClass {} @@ -509,25 +553,25 @@ export { D }; export function withProviders8() { return {ngModule: SomeModule}; } export {ExternalModule} from './module'; ` - }, - { - name: _('/node_modules/test-package/src/module.js'), - contents: ` + }, + { + name: _('/node_modules/test-package/src/module.js'), + contents: ` export class ExternalModule { static withProviders1() { return {ngModule: ExternalModule}; } static withProviders2() { return {ngModule: ExternalModule}; } }` - }, - { - name: _('/node_modules/some-library/index.d.ts'), - contents: 'export declare class LibraryModule {}' - }, - ]; + }, + { + name: _('/node_modules/some-library/index.d.ts'), + contents: 'export declare class LibraryModule {}' + }, + ]; - MODULE_WITH_PROVIDERS_DTS_PROGRAM = [ - { - name: _('/node_modules/test-package/typings/index.d.ts'), - contents: ` + MODULE_WITH_PROVIDERS_DTS_PROGRAM = [ + { + name: _('/node_modules/test-package/typings/index.d.ts'), + contents: ` import {ModuleWithProviders} from '@angular/core'; export declare class SomeClass {} export interface MyModuleWithProviders extends ModuleWithProviders {} @@ -551,41 +595,41 @@ export { D }; export declare function withProviders8(): MyModuleWithProviders; export {ExternalModule} from './module'; ` - }, - { - name: _('/node_modules/test-package/typings/module.d.ts'), - contents: ` + }, + { + name: _('/node_modules/test-package/typings/module.d.ts'), + contents: ` export interface ModuleWithProviders {} export declare class ExternalModule { static withProviders1(): ModuleWithProviders; static withProviders2(): ModuleWithProviders; }` - }, - { - name: _('/node_modules/some-library/index.d.ts'), - contents: 'export declare class LibraryModule {}' - }, - ]; - }); + }, + { + name: _('/node_modules/some-library/index.d.ts'), + contents: 'export declare class LibraryModule {}' + }, + ]; + }); - it('should fixup functions/methods that return ModuleWithProviders structures', () => { - const {bundle, renderer, host} = - setup(MODULE_WITH_PROVIDERS_PROGRAM, MODULE_WITH_PROVIDERS_DTS_PROGRAM); + it('should fixup functions/methods that return ModuleWithProviders structures', () => { + const {bundle, renderer, host} = + setup(MODULE_WITH_PROVIDERS_PROGRAM, MODULE_WITH_PROVIDERS_DTS_PROGRAM); - const referencesRegistry = new NgccReferencesRegistry(host); - const moduleWithProvidersAnalyses = - new ModuleWithProvidersAnalyzer( - host, bundle.src.program.getTypeChecker(), referencesRegistry, true) - .analyzeProgram(bundle.src.program); - const typingsFile = getSourceFileOrError( - bundle.dts!.program, _('/node_modules/test-package/typings/index.d.ts')); - const moduleWithProvidersInfo = moduleWithProvidersAnalyses.get(typingsFile)!; + const referencesRegistry = new NgccReferencesRegistry(host); + const moduleWithProvidersAnalyses = + new ModuleWithProvidersAnalyzer( + host, bundle.src.program.getTypeChecker(), referencesRegistry, true) + .analyzeProgram(bundle.src.program); + const typingsFile = getSourceFileOrError( + bundle.dts!.program, _('/node_modules/test-package/typings/index.d.ts')); + const moduleWithProvidersInfo = moduleWithProvidersAnalyses.get(typingsFile)!; - const output = new MagicString(MODULE_WITH_PROVIDERS_DTS_PROGRAM[0].contents); - const importManager = new ImportManager(new NoopImportRewriter(), 'i'); - renderer.addModuleWithProvidersParams(output, moduleWithProvidersInfo, importManager); + const output = new MagicString(MODULE_WITH_PROVIDERS_DTS_PROGRAM[0].contents); + const importManager = new ImportManager(new NoopImportRewriter(), 'i'); + renderer.addModuleWithProvidersParams(output, moduleWithProvidersInfo, importManager); - expect(output.toString()).toContain(` + expect(output.toString()).toContain(` static withProviders1(): ModuleWithProviders; static withProviders2(): ModuleWithProviders; static withProviders3(): ModuleWithProviders; @@ -594,7 +638,7 @@ export { D }; static withProviders6(): ModuleWithProviders; static withProviders7(): ({ngModule: SomeModule, providers: any[]})&{ngModule:SomeModule}; static withProviders8(): (MyModuleWithProviders)&{ngModule:SomeModule};`); - expect(output.toString()).toContain(` + expect(output.toString()).toContain(` export declare function withProviders1(): ModuleWithProviders; export declare function withProviders2(): ModuleWithProviders; export declare function withProviders3(): ModuleWithProviders; @@ -603,42 +647,44 @@ export { D }; export declare function withProviders6(): ModuleWithProviders; export declare function withProviders7(): ({ngModule: SomeModule, providers: any[]})&{ngModule:SomeModule}; export declare function withProviders8(): (MyModuleWithProviders)&{ngModule:SomeModule};`); - }); + }); - it('should not mistake `ModuleWithProviders` types that are not imported from `@angular/core', - () => { - const {bundle, renderer, host} = - setup(MODULE_WITH_PROVIDERS_PROGRAM, MODULE_WITH_PROVIDERS_DTS_PROGRAM); + it('should not mistake `ModuleWithProviders` types that are not imported from `@angular/core', + () => { + const {bundle, renderer, host} = + setup(MODULE_WITH_PROVIDERS_PROGRAM, MODULE_WITH_PROVIDERS_DTS_PROGRAM); - const referencesRegistry = new NgccReferencesRegistry(host); - const moduleWithProvidersAnalyses = - new ModuleWithProvidersAnalyzer( - host, bundle.src.program.getTypeChecker(), referencesRegistry, true) - .analyzeProgram(bundle.src.program); - const typingsFile = getSourceFileOrError( - bundle.dts!.program, _('/node_modules/test-package/typings/module.d.ts')); - const moduleWithProvidersInfo = moduleWithProvidersAnalyses.get(typingsFile)!; + const referencesRegistry = new NgccReferencesRegistry(host); + const moduleWithProvidersAnalyses = + new ModuleWithProvidersAnalyzer( + host, bundle.src.program.getTypeChecker(), referencesRegistry, true) + .analyzeProgram(bundle.src.program); + const typingsFile = getSourceFileOrError( + bundle.dts!.program, _('/node_modules/test-package/typings/module.d.ts')); + const moduleWithProvidersInfo = moduleWithProvidersAnalyses.get(typingsFile)!; - const output = new MagicString(MODULE_WITH_PROVIDERS_DTS_PROGRAM[1].contents); - const importManager = new ImportManager(new NoopImportRewriter(), 'i'); - renderer.addModuleWithProvidersParams(output, moduleWithProvidersInfo, importManager); - expect(output.toString()).toContain(` + const output = new MagicString(MODULE_WITH_PROVIDERS_DTS_PROGRAM[1].contents); + const importManager = new ImportManager(new NoopImportRewriter(), 'i'); + renderer.addModuleWithProvidersParams(output, moduleWithProvidersInfo, importManager); + expect(output.toString()).toContain(` static withProviders1(): (ModuleWithProviders)&{ngModule:ExternalModule}; static withProviders2(): (ModuleWithProviders)&{ngModule:ExternalModule};`); - }); - }); + }); + }); - describe('printStatement', () => { - it('should transpile code to ES2015', () => { - const {renderer, sourceFile, importManager} = setup([PROGRAM]); + describe('printStatement', () => { + it('should transpile code to ES2015', () => { + const {renderer, sourceFile, importManager} = setup([PROGRAM]); - const stmt1 = new DeclareVarStmt('foo', new LiteralExpr(42), null, [StmtModifier.Final]); - 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.Final]); + const stmt2 = new DeclareVarStmt('bar', new LiteralExpr(true)); + const stmt3 = new DeclareVarStmt('baz', new LiteralExpr('qux'), undefined, []); - expect(renderer.printStatement(stmt1, sourceFile, importManager)).toBe('const 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('const foo = 42;'); + expect(renderer.printStatement(stmt2, sourceFile, importManager)).toBe('var bar = true;'); + expect(renderer.printStatement(stmt3, sourceFile, importManager)) + .toBe('var baz = "qux";'); + }); }); }); }); 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 b260b54b99..2df0424d4b 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 @@ -481,8 +481,11 @@ SOME DEFINITION TEXT program, absoluteFromSourceFile(sourceFile), 'NoIife', ts.isFunctionDeclaration); const mockNoIifeClass: any = {declaration: noIifeDeclaration, name: 'NoIife'}; expect(() => renderer.addDefinitions(output, mockNoIifeClass, 'SOME DEFINITION TEXT')) - .toThrowError(`Compiled class declaration is not inside an IIFE: NoIife in ${ - _('/node_modules/test-package/some/file.js')}`); + .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);