1049 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			1049 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /**
 | |
|  * @license
 | |
|  * Copyright Google Inc. All Rights Reserved.
 | |
|  *
 | |
|  * Use of this source code is governed by an MIT-style license that can be
 | |
|  * found in the LICENSE file at https://angular.io/license
 | |
|  */
 | |
| 
 | |
| import * as ng from '@angular/compiler-cli';
 | |
| import * as fs from 'fs';
 | |
| import * as path from 'path';
 | |
| import * as ts from 'typescript';
 | |
| 
 | |
| import {formatDiagnostics} from '../../src/perform_compile';
 | |
| import {CompilerHost, EmitFlags, LazyRoute} from '../../src/transformers/api';
 | |
| import {createSrcToOutPathMapper} from '../../src/transformers/program';
 | |
| import {GENERATED_FILES, StructureIsReused, tsStructureIsReused} from '../../src/transformers/util';
 | |
| import {TestSupport, expectNoDiagnosticsInProgram, setup} from '../test_support';
 | |
| 
 | |
| describe('ng program', () => {
 | |
|   let testSupport: TestSupport;
 | |
|   let errorSpy: jasmine.Spy&((s: string) => void);
 | |
| 
 | |
|   beforeEach(() => {
 | |
|     errorSpy = jasmine.createSpy('consoleError').and.callFake(console.error);
 | |
|     testSupport = setup();
 | |
|   });
 | |
| 
 | |
|   function createModuleAndCompSource(prefix: string, template: string = prefix + 'template') {
 | |
|     const templateEntry =
 | |
|         template.endsWith('.html') ? `templateUrl: '${template}'` : `template: \`${template}\``;
 | |
|     return `
 | |
|       import {Component, NgModule} from '@angular/core';
 | |
| 
 | |
|       @Component({selector: '${prefix}', ${templateEntry}})
 | |
|       export class ${prefix}Comp {}
 | |
| 
 | |
|       @NgModule({declarations: [${prefix}Comp]})
 | |
|       export class ${prefix}Module {}
 | |
|     `;
 | |
|   }
 | |
| 
 | |
|   function compileLib(libName: string) {
 | |
|     testSupport.writeFiles({
 | |
|       [`${libName}_src/index.ts`]: createModuleAndCompSource(libName),
 | |
|     });
 | |
|     const options = testSupport.createCompilerOptions();
 | |
|     const program = ng.createProgram({
 | |
|       rootNames: [path.resolve(testSupport.basePath, `${libName}_src/index.ts`)],
 | |
|       options,
 | |
|       host: ng.createCompilerHost({options}),
 | |
|     });
 | |
|     expectNoDiagnosticsInProgram(options, program);
 | |
|     fs.symlinkSync(
 | |
|         path.resolve(testSupport.basePath, 'built', `${libName}_src`),
 | |
|         path.resolve(testSupport.basePath, 'node_modules', libName));
 | |
|     program.emit({emitFlags: ng.EmitFlags.DTS | ng.EmitFlags.JS | ng.EmitFlags.Metadata});
 | |
|   }
 | |
| 
 | |
|   function compile(
 | |
|       oldProgram?: ng.Program, overrideOptions?: ng.CompilerOptions, rootNames?: string[],
 | |
|       host?: CompilerHost): {program: ng.Program, emitResult: ts.EmitResult} {
 | |
|     const options = testSupport.createCompilerOptions(overrideOptions);
 | |
|     if (!rootNames) {
 | |
|       rootNames = [path.resolve(testSupport.basePath, 'src/index.ts')];
 | |
|     }
 | |
|     if (!host) {
 | |
|       host = ng.createCompilerHost({options});
 | |
|     }
 | |
|     const program = ng.createProgram({
 | |
|       rootNames: rootNames,
 | |
|       options,
 | |
|       host,
 | |
|       oldProgram,
 | |
|     });
 | |
|     expectNoDiagnosticsInProgram(options, program);
 | |
|     const emitResult = program.emit();
 | |
|     return {emitResult, program};
 | |
|   }
 | |
| 
 | |
|   function resolveFiles(rootNames: string[]) {
 | |
|     const preOptions = testSupport.createCompilerOptions();
 | |
|     const preHost = ts.createCompilerHost(preOptions);
 | |
|     // don't resolve symlinks
 | |
|     preHost.realpath = (f) => f;
 | |
|     const preProgram = ts.createProgram(rootNames, preOptions, preHost);
 | |
|     return preProgram.getSourceFiles().map(sf => sf.fileName);
 | |
|   }
 | |
| 
 | |
|   describe('reuse of old program', () => {
 | |
|     it('should reuse generated code for libraries from old programs', () => {
 | |
|       compileLib('lib');
 | |
|       testSupport.writeFiles({
 | |
|         'src/main.ts': createModuleAndCompSource('main'),
 | |
|         'src/index.ts': `
 | |
|             export * from './main';
 | |
|             export * from 'lib/index';
 | |
|           `
 | |
|       });
 | |
|       const p1 = compile().program;
 | |
|       expect(p1.getTsProgram().getSourceFiles().some(
 | |
|                  sf => /node_modules\/lib\/.*\.ngfactory\.ts$/.test(sf.fileName)))
 | |
|           .toBe(true);
 | |
|       expect(p1.getTsProgram().getSourceFiles().some(
 | |
|                  sf => /node_modules\/lib2\/.*\.ngfactory.*$/.test(sf.fileName)))
 | |
|           .toBe(false);
 | |
|       const p2 = compile(p1).program;
 | |
|       expect(p2.getTsProgram().getSourceFiles().some(
 | |
|                  sf => /node_modules\/lib\/.*\.ngfactory.*$/.test(sf.fileName)))
 | |
|           .toBe(false);
 | |
|       expect(p2.getTsProgram().getSourceFiles().some(
 | |
|                  sf => /node_modules\/lib2\/.*\.ngfactory.*$/.test(sf.fileName)))
 | |
|           .toBe(false);
 | |
| 
 | |
|       // import a library for which we didn't generate code before
 | |
|       compileLib('lib2');
 | |
|       testSupport.writeFiles({
 | |
|         'src/index.ts': `
 | |
|           export * from './main';
 | |
|           export * from 'lib/index';
 | |
|           export * from 'lib2/index';
 | |
|         `,
 | |
|       });
 | |
|       const p3 = compile(p2).program;
 | |
|       expect(p3.getTsProgram().getSourceFiles().some(
 | |
|                  sf => /node_modules\/lib\/.*\.ngfactory.*$/.test(sf.fileName)))
 | |
|           .toBe(false);
 | |
|       expect(p3.getTsProgram().getSourceFiles().some(
 | |
|                  sf => /node_modules\/lib2\/.*\.ngfactory\.ts$/.test(sf.fileName)))
 | |
|           .toBe(true);
 | |
| 
 | |
|       const p4 = compile(p3).program;
 | |
|       expect(p4.getTsProgram().getSourceFiles().some(
 | |
|                  sf => /node_modules\/lib\/.*\.ngfactory.*$/.test(sf.fileName)))
 | |
|           .toBe(false);
 | |
|       expect(p4.getTsProgram().getSourceFiles().some(
 | |
|                  sf => /node_modules\/lib2\/.*\.ngfactory.*$/.test(sf.fileName)))
 | |
|           .toBe(false);
 | |
|     });
 | |
| 
 | |
|     // Note: this is the case for watch mode with declaration:false
 | |
|     it('should reuse generated code from libraries from old programs with declaration:false',
 | |
|        () => {
 | |
|          compileLib('lib');
 | |
| 
 | |
|          testSupport.writeFiles({
 | |
|            'src/main.ts': createModuleAndCompSource('main'),
 | |
|            'src/index.ts': `
 | |
|             export * from './main';
 | |
|             export * from 'lib/index';
 | |
|           `
 | |
|          });
 | |
|          const p1 = compile(undefined, {declaration: false}).program;
 | |
|          expect(p1.getTsProgram().getSourceFiles().some(
 | |
|                     sf => /node_modules\/lib\/.*\.ngfactory\.ts$/.test(sf.fileName)))
 | |
|              .toBe(true);
 | |
|          expect(p1.getTsProgram().getSourceFiles().some(
 | |
|                     sf => /node_modules\/lib2\/.*\.ngfactory.*$/.test(sf.fileName)))
 | |
|              .toBe(false);
 | |
|          const p2 = compile(p1, {declaration: false}).program;
 | |
|          expect(p2.getTsProgram().getSourceFiles().some(
 | |
|                     sf => /node_modules\/lib\/.*\.ngfactory.*$/.test(sf.fileName)))
 | |
|              .toBe(false);
 | |
|          expect(p2.getTsProgram().getSourceFiles().some(
 | |
|                     sf => /node_modules\/lib2\/.*\.ngfactory.*$/.test(sf.fileName)))
 | |
|              .toBe(false);
 | |
|        });
 | |
| 
 | |
|     it('should only emit changed files', () => {
 | |
|       testSupport.writeFiles({
 | |
|         'src/index.ts': createModuleAndCompSource('comp', 'index.html'),
 | |
|         'src/index.html': `Start`
 | |
|       });
 | |
|       const options: ng.CompilerOptions = {declaration: false};
 | |
|       const host = ng.createCompilerHost({options});
 | |
|       const originalGetSourceFile = host.getSourceFile;
 | |
|       const fileCache = new Map<string, ts.SourceFile>();
 | |
|       host.getSourceFile = (fileName: string) => {
 | |
|         if (fileCache.has(fileName)) {
 | |
|           return fileCache.get(fileName);
 | |
|         }
 | |
|         const sf = originalGetSourceFile.call(host, fileName);
 | |
|         fileCache.set(fileName, sf);
 | |
|         return sf;
 | |
|       };
 | |
| 
 | |
|       const written = new Map<string, string>();
 | |
|       host.writeFile = (fileName: string, data: string) => written.set(fileName, data);
 | |
| 
 | |
|       // compile libraries
 | |
|       const p1 = compile(undefined, options, undefined, host).program;
 | |
| 
 | |
|       // compile without libraries
 | |
|       const p2 = compile(p1, options, undefined, host).program;
 | |
|       expect(written.has(path.resolve(testSupport.basePath, 'built/src/index.js'))).toBe(true);
 | |
|       let ngFactoryContent =
 | |
|           written.get(path.resolve(testSupport.basePath, 'built/src/index.ngfactory.js'));
 | |
|       expect(ngFactoryContent).toMatch(/Start/);
 | |
| 
 | |
|       // no change -> no emit
 | |
|       written.clear();
 | |
|       const p3 = compile(p2, options, undefined, host).program;
 | |
|       expect(written.size).toBe(0);
 | |
| 
 | |
|       // change a user file
 | |
|       written.clear();
 | |
|       fileCache.delete(path.resolve(testSupport.basePath, 'src/index.ts'));
 | |
|       const p4 = compile(p3, options, undefined, host).program;
 | |
|       expect(written.size).toBe(1);
 | |
|       expect(written.has(path.resolve(testSupport.basePath, 'built/src/index.js'))).toBe(true);
 | |
| 
 | |
|       // change a file that is input to generated files
 | |
|       written.clear();
 | |
|       testSupport.writeFiles({'src/index.html': 'Hello'});
 | |
|       const p5 = compile(p4, options, undefined, host).program;
 | |
|       expect(written.size).toBe(1);
 | |
|       ngFactoryContent =
 | |
|           written.get(path.resolve(testSupport.basePath, 'built/src/index.ngfactory.js'));
 | |
|       expect(ngFactoryContent).toMatch(/Hello/);
 | |
| 
 | |
|       // change a file and create an intermediate program that is not emitted
 | |
|       written.clear();
 | |
|       fileCache.delete(path.resolve(testSupport.basePath, 'src/index.ts'));
 | |
|       const p6 = ng.createProgram({
 | |
|         rootNames: [path.resolve(testSupport.basePath, 'src/index.ts')],
 | |
|         options: testSupport.createCompilerOptions(options), host,
 | |
|         oldProgram: p5
 | |
|       });
 | |
|       const p7 = compile(p6, options, undefined, host).program;
 | |
|       expect(written.size).toBe(1);
 | |
|     });
 | |
| 
 | |
|     it('should set emitSkipped to false for full and incremental emit', () => {
 | |
|       testSupport.writeFiles({
 | |
|         'src/index.ts': createModuleAndCompSource('main'),
 | |
|       });
 | |
|       const {emitResult: emitResult1, program: p1} = compile();
 | |
|       expect(emitResult1.emitSkipped).toBe(false);
 | |
|       const {emitResult: emitResult2, program: p2} = compile(p1);
 | |
|       expect(emitResult2.emitSkipped).toBe(false);
 | |
|       const {emitResult: emitResult3, program: p3} = compile(p2);
 | |
|       expect(emitResult3.emitSkipped).toBe(false);
 | |
|     });
 | |
| 
 | |
|     it('should store library summaries on emit', () => {
 | |
|       compileLib('lib');
 | |
|       testSupport.writeFiles({
 | |
|         'src/main.ts': createModuleAndCompSource('main'),
 | |
|         'src/index.ts': `
 | |
|             export * from './main';
 | |
|             export * from 'lib/index';
 | |
|           `
 | |
|       });
 | |
|       const p1 = compile().program;
 | |
|       expect(Array.from(p1.getLibrarySummaries().values())
 | |
|                  .some(sf => /node_modules\/lib\/index\.ngfactory\.d\.ts$/.test(sf.fileName)))
 | |
|           .toBe(true);
 | |
|       expect(Array.from(p1.getLibrarySummaries().values())
 | |
|                  .some(sf => /node_modules\/lib\/index\.ngsummary\.json$/.test(sf.fileName)))
 | |
|           .toBe(true);
 | |
|       expect(Array.from(p1.getLibrarySummaries().values())
 | |
|                  .some(sf => /node_modules\/lib\/index\.d\.ts$/.test(sf.fileName)))
 | |
|           .toBe(true);
 | |
| 
 | |
|       expect(Array.from(p1.getLibrarySummaries().values())
 | |
|                  .some(sf => /src\/main.*$/.test(sf.fileName)))
 | |
|           .toBe(false);
 | |
|     });
 | |
| 
 | |
|     it('should reuse the old ts program completely if nothing changed', () => {
 | |
|       testSupport.writeFiles({'src/index.ts': createModuleAndCompSource('main')});
 | |
|       // Note: the second compile drops factories for library files,
 | |
|       // and therefore changes the structure again
 | |
|       const p1 = compile().program;
 | |
|       const p2 = compile(p1).program;
 | |
|       compile(p2);
 | |
|       expect(tsStructureIsReused(p2.getTsProgram())).toBe(StructureIsReused.Completely);
 | |
|     });
 | |
| 
 | |
|     it('should reuse the old ts program completely if a template or a ts file changed', () => {
 | |
|       testSupport.writeFiles({
 | |
|         'src/main.ts': createModuleAndCompSource('main', 'main.html'),
 | |
|         'src/main.html': `Some template`,
 | |
|         'src/util.ts': `export const x = 1`,
 | |
|         'src/index.ts': `
 | |
|           export * from './main';
 | |
|           export * from './util';
 | |
|         `
 | |
|       });
 | |
|       // Note: the second compile drops factories for library files,
 | |
|       // and therefore changes the structure again
 | |
|       const p1 = compile().program;
 | |
|       const p2 = compile(p1).program;
 | |
|       testSupport.writeFiles({
 | |
|         'src/main.html': `Another template`,
 | |
|         'src/util.ts': `export const x = 2`,
 | |
|       });
 | |
|       compile(p2);
 | |
|       expect(tsStructureIsReused(p2.getTsProgram())).toBe(StructureIsReused.Completely);
 | |
|     });
 | |
| 
 | |
|     it('should not reuse the old ts program if an import changed', () => {
 | |
|       testSupport.writeFiles({
 | |
|         'src/main.ts': createModuleAndCompSource('main'),
 | |
|         'src/util.ts': `export const x = 1`,
 | |
|         'src/index.ts': `
 | |
|           export * from './main';
 | |
|           export * from './util';
 | |
|         `
 | |
|       });
 | |
|       // Note: the second compile drops factories for library files,
 | |
|       // and therefore changes the structure again
 | |
|       const p1 = compile().program;
 | |
|       const p2 = compile(p1).program;
 | |
|       testSupport.writeFiles(
 | |
|           {'src/util.ts': `import {Injectable} from '@angular/core'; export const x = 1;`});
 | |
|       compile(p2);
 | |
|       expect(tsStructureIsReused(p2.getTsProgram())).toBe(StructureIsReused.SafeModules);
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   it('should not typecheck templates if skipTemplateCodegen is set but fullTemplateTypeCheck is not',
 | |
|      () => {
 | |
|        testSupport.writeFiles({
 | |
|          'src/main.ts': `
 | |
|         import {NgModule} from '@angular/core';
 | |
| 
 | |
|         @NgModule((() => {if (1==1) return null as any;}) as any)
 | |
|         export class SomeClassWithInvalidMetadata {}
 | |
|       `,
 | |
|        });
 | |
|        const options = testSupport.createCompilerOptions({skipTemplateCodegen: true});
 | |
|        const host = ng.createCompilerHost({options});
 | |
|        const program = ng.createProgram(
 | |
|            {rootNames: [path.resolve(testSupport.basePath, 'src/main.ts')], options, host});
 | |
|        expectNoDiagnosticsInProgram(options, program);
 | |
|        const emitResult = program.emit({emitFlags: EmitFlags.All});
 | |
|        expect(emitResult.diagnostics.length).toBe(0);
 | |
| 
 | |
|        testSupport.shouldExist('built/src/main.metadata.json');
 | |
|      });
 | |
| 
 | |
|   it('should typecheck templates if skipTemplateCodegen and fullTemplateTypeCheck is set', () => {
 | |
|     testSupport.writeFiles({
 | |
|       'src/main.ts': createModuleAndCompSource('main', `{{nonExistent}}`),
 | |
|     });
 | |
|     const options = testSupport.createCompilerOptions({
 | |
|       skipTemplateCodegen: true,
 | |
|       fullTemplateTypeCheck: true,
 | |
|     });
 | |
|     const host = ng.createCompilerHost({options});
 | |
|     const program = ng.createProgram(
 | |
|         {rootNames: [path.resolve(testSupport.basePath, 'src/main.ts')], options, host});
 | |
|     const diags = program.getNgSemanticDiagnostics();
 | |
|     expect(diags.length).toBe(1);
 | |
|     expect(diags[0].messageText).toBe(`Property 'nonExistent' does not exist on type 'mainComp'.`);
 | |
|   });
 | |
| 
 | |
|   it('should be able to use asynchronously loaded resources', (done) => {
 | |
|     testSupport.writeFiles({
 | |
|       'src/main.ts': createModuleAndCompSource('main', 'main.html'),
 | |
|       // Note: we need to be able to resolve the template synchronously,
 | |
|       // only the content is delivered asynchronously.
 | |
|       'src/main.html': '',
 | |
|     });
 | |
|     const options = testSupport.createCompilerOptions();
 | |
|     const host = ng.createCompilerHost({options});
 | |
|     host.readResource = () => Promise.resolve('Hello world!');
 | |
|     const program = ng.createProgram(
 | |
|         {rootNames: [path.resolve(testSupport.basePath, 'src/main.ts')], options, host});
 | |
|     program.loadNgStructureAsync().then(() => {
 | |
|       program.emit();
 | |
|       const factory =
 | |
|           fs.readFileSync(path.resolve(testSupport.basePath, 'built/src/main.ngfactory.js'));
 | |
|       expect(factory).toContain('Hello world!');
 | |
|       done();
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   it('should work with noResolve', () => {
 | |
|     // create a temporary ts program to get the list of all files from angular...
 | |
|     testSupport.writeFiles({
 | |
|       'src/main.ts': createModuleAndCompSource('main'),
 | |
|     });
 | |
|     const allRootNames = resolveFiles([path.resolve(testSupport.basePath, 'src/main.ts')]);
 | |
| 
 | |
|     // now do the actual test with noResolve
 | |
|     const program = compile(undefined, {noResolve: true}, allRootNames);
 | |
| 
 | |
|     testSupport.shouldExist('built/src/main.ngfactory.js');
 | |
|     testSupport.shouldExist('built/src/main.ngfactory.d.ts');
 | |
|   });
 | |
| 
 | |
|   it('should work with tsx files', () => {
 | |
|     // create a temporary ts program to get the list of all files from angular...
 | |
|     testSupport.writeFiles({
 | |
|       'src/main.tsx': createModuleAndCompSource('main'),
 | |
|     });
 | |
|     const allRootNames = resolveFiles([path.resolve(testSupport.basePath, 'src/main.tsx')]);
 | |
| 
 | |
|     const program = compile(undefined, {jsx: ts.JsxEmit.React}, allRootNames);
 | |
| 
 | |
|     testSupport.shouldExist('built/src/main.js');
 | |
|     testSupport.shouldExist('built/src/main.d.ts');
 | |
|     testSupport.shouldExist('built/src/main.ngfactory.js');
 | |
|     testSupport.shouldExist('built/src/main.ngfactory.d.ts');
 | |
|     testSupport.shouldExist('built/src/main.ngsummary.json');
 | |
|   });
 | |
| 
 | |
|   it('should emit also empty generated files depending on the options', () => {
 | |
|     testSupport.writeFiles({
 | |
|       'src/main.ts': `
 | |
|         import {Component, NgModule} from '@angular/core';
 | |
| 
 | |
|         @Component({selector: 'main', template: '', styleUrls: ['main.css']})
 | |
|         export class MainComp {}
 | |
| 
 | |
|         @NgModule({declarations: [MainComp]})
 | |
|         export class MainModule {}
 | |
|       `,
 | |
|       'src/main.css': ``,
 | |
|       'src/util.ts': 'export const x = 1;',
 | |
|       'src/index.ts': `
 | |
|         export * from './util';
 | |
|         export * from './main';
 | |
|       `,
 | |
|     });
 | |
|     const options = testSupport.createCompilerOptions({
 | |
|       allowEmptyCodegenFiles: true,
 | |
|       enableSummariesForJit: true,
 | |
|     });
 | |
|     const host = ng.createCompilerHost({options});
 | |
|     const written = new Map < string, {
 | |
|       original: ReadonlyArray<ts.SourceFile>|undefined;
 | |
|       data: string;
 | |
|     }
 | |
|     > ();
 | |
| 
 | |
|     host.writeFile =
 | |
|         (fileName: string, data: string, writeByteOrderMark: boolean,
 | |
|          onError: (message: string) => void|undefined,
 | |
|          sourceFiles: ReadonlyArray<ts.SourceFile>) => {
 | |
|           written.set(fileName, {original: sourceFiles, data});
 | |
|         };
 | |
|     const program = ng.createProgram(
 | |
|         {rootNames: [path.resolve(testSupport.basePath, 'src/index.ts')], options, host});
 | |
|     program.emit();
 | |
| 
 | |
|     const enum ShouldBe { Empty, EmptyExport, NoneEmpty }
 | |
|     function assertGenFile(
 | |
|         fileName: string, checks: {originalFileName: string, shouldBe: ShouldBe}) {
 | |
|       const writeData = written.get(path.join(testSupport.basePath, fileName));
 | |
|       expect(writeData).toBeTruthy();
 | |
|       expect(writeData !.original !.some(
 | |
|                  sf => sf.fileName === path.join(testSupport.basePath, checks.originalFileName)))
 | |
|           .toBe(true);
 | |
|       switch (checks.shouldBe) {
 | |
|         case ShouldBe.Empty:
 | |
|           expect(writeData !.data).toMatch(/^(\s*\/\*([^*]|\*[^/])*\*\/\s*)?$/);
 | |
|           break;
 | |
|         case ShouldBe.EmptyExport:
 | |
|           expect(writeData !.data)
 | |
|               .toMatch(/^((\s*\/\*([^*]|\*[^/])*\*\/\s*)|(\s*export\s*{\s*}\s*;\s*)|())$/);
 | |
|           break;
 | |
|         case ShouldBe.NoneEmpty:
 | |
|           expect(writeData !.data).not.toBe('');
 | |
|           break;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     assertGenFile(
 | |
|         'built/src/util.ngfactory.js', {originalFileName: 'src/util.ts', shouldBe: ShouldBe.Empty});
 | |
|     assertGenFile(
 | |
|         'built/src/util.ngfactory.d.ts',
 | |
|         {originalFileName: 'src/util.ts', shouldBe: ShouldBe.EmptyExport});
 | |
|     assertGenFile(
 | |
|         'built/src/util.ngsummary.js', {originalFileName: 'src/util.ts', shouldBe: ShouldBe.Empty});
 | |
|     assertGenFile(
 | |
|         'built/src/util.ngsummary.d.ts',
 | |
|         {originalFileName: 'src/util.ts', shouldBe: ShouldBe.EmptyExport});
 | |
|     assertGenFile(
 | |
|         'built/src/util.ngsummary.json',
 | |
|         {originalFileName: 'src/util.ts', shouldBe: ShouldBe.NoneEmpty});
 | |
| 
 | |
|     // Note: we always fill non shim and shim style files as they might
 | |
|     // be shared by component with and without ViewEncapsulation.
 | |
|     assertGenFile(
 | |
|         'built/src/main.css.ngstyle.js',
 | |
|         {originalFileName: 'src/main.ts', shouldBe: ShouldBe.NoneEmpty});
 | |
|     assertGenFile(
 | |
|         'built/src/main.css.ngstyle.d.ts',
 | |
|         {originalFileName: 'src/main.ts', shouldBe: ShouldBe.EmptyExport});
 | |
|     // Note: this file is not empty as we actually generated code for it
 | |
|     assertGenFile(
 | |
|         'built/src/main.css.shim.ngstyle.js',
 | |
|         {originalFileName: 'src/main.ts', shouldBe: ShouldBe.NoneEmpty});
 | |
|     assertGenFile(
 | |
|         'built/src/main.css.shim.ngstyle.d.ts',
 | |
|         {originalFileName: 'src/main.ts', shouldBe: ShouldBe.EmptyExport});
 | |
|   });
 | |
| 
 | |
|   it('should not emit /// references in .d.ts files', () => {
 | |
|     testSupport.writeFiles({
 | |
|       'src/main.ts': createModuleAndCompSource('main'),
 | |
|     });
 | |
|     compile(undefined, {declaration: true}, [path.resolve(testSupport.basePath, 'src/main.ts')]);
 | |
| 
 | |
|     const dts =
 | |
|         fs.readFileSync(path.resolve(testSupport.basePath, 'built', 'src', 'main.d.ts')).toString();
 | |
|     expect(dts).toMatch('export declare class');
 | |
|     expect(dts).not.toMatch('///');
 | |
|   });
 | |
| 
 | |
|   it('should not emit generated files whose sources are outside of the rootDir', () => {
 | |
|     testSupport.writeFiles({
 | |
|       'src/main.ts': createModuleAndCompSource('main'),
 | |
|       'src/index.ts': `
 | |
|           export * from './main';
 | |
|         `
 | |
|     });
 | |
|     const options =
 | |
|         testSupport.createCompilerOptions({rootDir: path.resolve(testSupport.basePath, 'src')});
 | |
|     const host = ng.createCompilerHost({options});
 | |
|     const writtenFileNames: string[] = [];
 | |
|     const oldWriteFile = host.writeFile;
 | |
|     host.writeFile = (fileName, data, writeByteOrderMark, onError, sourceFiles) => {
 | |
|       writtenFileNames.push(fileName);
 | |
|       oldWriteFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
 | |
|     };
 | |
| 
 | |
|     compile(/*oldProgram*/ undefined, options, /*rootNames*/ undefined, host);
 | |
| 
 | |
|     // no emit for files from node_modules as they are outside of rootDir
 | |
|     expect(writtenFileNames.some(f => /node_modules/.test(f))).toBe(false);
 | |
| 
 | |
|     // emit all gen files for files under src/
 | |
|     testSupport.shouldExist('built/main.js');
 | |
|     testSupport.shouldExist('built/main.d.ts');
 | |
|     testSupport.shouldExist('built/main.ngfactory.js');
 | |
|     testSupport.shouldExist('built/main.ngfactory.d.ts');
 | |
|     testSupport.shouldExist('built/main.ngsummary.json');
 | |
|   });
 | |
| 
 | |
|   describe('createSrcToOutPathMapper', () => {
 | |
|     it('should return identity mapping if no outDir is present', () => {
 | |
|       const mapper = createSrcToOutPathMapper(undefined, undefined, undefined);
 | |
|       expect(mapper('/tmp/b/y.js')).toBe('/tmp/b/y.js');
 | |
|     });
 | |
| 
 | |
|     it('should return identity mapping if first src and out fileName have same dir', () => {
 | |
|       const mapper = createSrcToOutPathMapper('/tmp', '/tmp/a/x.ts', '/tmp/a/x.js');
 | |
|       expect(mapper('/tmp/b/y.js')).toBe('/tmp/b/y.js');
 | |
|     });
 | |
| 
 | |
|     it('should adjust the filename if the outDir is inside of the rootDir', () => {
 | |
|       const mapper = createSrcToOutPathMapper('/tmp/out', '/tmp/a/x.ts', '/tmp/out/a/x.js');
 | |
|       expect(mapper('/tmp/b/y.js')).toBe('/tmp/out/b/y.js');
 | |
|     });
 | |
| 
 | |
|     it('should adjust the filename if the outDir is outside of the rootDir', () => {
 | |
|       const mapper = createSrcToOutPathMapper('/out', '/tmp/a/x.ts', '/out/a/x.js');
 | |
|       expect(mapper('/tmp/b/y.js')).toBe('/out/b/y.js');
 | |
|     });
 | |
| 
 | |
|     it('should adjust the filename if the common prefix of sampleSrc and sampleOut is outside of outDir',
 | |
|        () => {
 | |
|          const mapper =
 | |
|              createSrcToOutPathMapper('/dist/common', '/src/common/x.ts', '/dist/common/x.js');
 | |
|          expect(mapper('/src/common/y.js')).toBe('/dist/common/y.js');
 | |
|        });
 | |
| 
 | |
|     it('should work on windows with normalized paths', () => {
 | |
|       const mapper =
 | |
|           createSrcToOutPathMapper('c:/tmp/out', 'c:/tmp/a/x.ts', 'c:/tmp/out/a/x.js', path.win32);
 | |
|       expect(mapper('c:/tmp/b/y.js')).toBe('c:\\tmp\\out\\b\\y.js');
 | |
|     });
 | |
| 
 | |
|     it('should work on windows with non-normalized paths', () => {
 | |
|       const mapper = createSrcToOutPathMapper(
 | |
|           'c:\\tmp\\out', 'c:\\tmp\\a\\x.ts', 'c:\\tmp\\out\\a\\x.js', path.win32);
 | |
|       expect(mapper('c:\\tmp\\b\\y.js')).toBe('c:\\tmp\\out\\b\\y.js');
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('listLazyRoutes', () => {
 | |
|     function writeSomeRoutes() {
 | |
|       testSupport.writeFiles({
 | |
|         'src/main.ts': `
 | |
|           import {NgModule} from '@angular/core';
 | |
|           import {RouterModule} from '@angular/router';
 | |
| 
 | |
|           @NgModule({
 | |
|             imports: [RouterModule.forRoot([{loadChildren: './child#ChildModule'}])]
 | |
|           })
 | |
|           export class MainModule {}
 | |
|         `,
 | |
|         'src/child.ts': `
 | |
|           import {NgModule} from '@angular/core';
 | |
|           import {RouterModule} from '@angular/router';
 | |
| 
 | |
|           @NgModule({
 | |
|             imports: [RouterModule.forChild([{loadChildren: './child2#ChildModule2'}])]
 | |
|           })
 | |
|           export class ChildModule {}
 | |
|         `,
 | |
|         'src/child2.ts': `
 | |
|           import {NgModule} from '@angular/core';
 | |
| 
 | |
|           @NgModule()
 | |
|           export class ChildModule2 {}
 | |
|         `,
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     function createProgram(rootNames: string[], overrideOptions: ng.CompilerOptions = {}) {
 | |
|       const options = testSupport.createCompilerOptions(overrideOptions);
 | |
|       const host = ng.createCompilerHost({options});
 | |
|       const program = ng.createProgram(
 | |
|           {rootNames: rootNames.map(p => path.resolve(testSupport.basePath, p)), options, host});
 | |
|       return {program, options};
 | |
|     }
 | |
| 
 | |
|     function normalizeRoutes(lazyRoutes: LazyRoute[]) {
 | |
|       return lazyRoutes.map(
 | |
|           r => ({
 | |
|             route: r.route,
 | |
|             module: {name: r.module.name, filePath: r.module.filePath},
 | |
|             referencedModule:
 | |
|                 {name: r.referencedModule.name, filePath: r.referencedModule.filePath},
 | |
|           }));
 | |
|     }
 | |
| 
 | |
|     it('should list all lazyRoutes', () => {
 | |
|       writeSomeRoutes();
 | |
|       const {program, options} = createProgram(['src/main.ts', 'src/child.ts', 'src/child2.ts']);
 | |
|       expectNoDiagnosticsInProgram(options, program);
 | |
|       expect(normalizeRoutes(program.listLazyRoutes())).toEqual([
 | |
|         {
 | |
|           module: {name: 'MainModule', filePath: path.resolve(testSupport.basePath, 'src/main.ts')},
 | |
|           referencedModule:
 | |
|               {name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
 | |
|           route: './child#ChildModule'
 | |
|         },
 | |
|         {
 | |
|           module:
 | |
|               {name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
 | |
|           referencedModule:
 | |
|               {name: 'ChildModule2', filePath: path.resolve(testSupport.basePath, 'src/child2.ts')},
 | |
|           route: './child2#ChildModule2'
 | |
|         },
 | |
|       ]);
 | |
|     });
 | |
| 
 | |
|     it('should emit correctly after listing lazyRoutes', () => {
 | |
|       testSupport.writeFiles({
 | |
|         'src/main.ts': `
 | |
|           import {NgModule} from '@angular/core';
 | |
|           import {RouterModule} from '@angular/router';
 | |
| 
 | |
|           @NgModule({
 | |
|             imports: [RouterModule.forRoot([{loadChildren: './lazy/lazy#LazyModule'}])]
 | |
|           })
 | |
|           export class MainModule {}
 | |
|         `,
 | |
|         'src/lazy/lazy.ts': `
 | |
|           import {NgModule} from '@angular/core';
 | |
| 
 | |
|           @NgModule()
 | |
|           export class ChildModule {}
 | |
|         `,
 | |
|       });
 | |
|       const {program, options} = createProgram(['src/main.ts', 'src/lazy/lazy.ts']);
 | |
|       expectNoDiagnosticsInProgram(options, program);
 | |
|       program.listLazyRoutes();
 | |
|       program.emit();
 | |
| 
 | |
|       const lazyNgFactory =
 | |
|           fs.readFileSync(path.resolve(testSupport.basePath, 'built/src/lazy/lazy.ngfactory.js'));
 | |
|       expect(lazyNgFactory).toContain('import * as i1 from "./lazy";');
 | |
|     });
 | |
| 
 | |
|     it('should list lazyRoutes given an entryRoute recursively', () => {
 | |
|       writeSomeRoutes();
 | |
|       const {program, options} = createProgram(['src/main.ts']);
 | |
|       expectNoDiagnosticsInProgram(options, program);
 | |
|       expect(normalizeRoutes(program.listLazyRoutes('src/main#MainModule'))).toEqual([
 | |
|         {
 | |
|           module: {name: 'MainModule', filePath: path.resolve(testSupport.basePath, 'src/main.ts')},
 | |
|           referencedModule:
 | |
|               {name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
 | |
|           route: './child#ChildModule'
 | |
|         },
 | |
|         {
 | |
|           module:
 | |
|               {name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
 | |
|           referencedModule:
 | |
|               {name: 'ChildModule2', filePath: path.resolve(testSupport.basePath, 'src/child2.ts')},
 | |
|           route: './child2#ChildModule2'
 | |
|         },
 | |
|       ]);
 | |
| 
 | |
|       expect(normalizeRoutes(program.listLazyRoutes('src/child#ChildModule'))).toEqual([
 | |
|         {
 | |
|           module:
 | |
|               {name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
 | |
|           referencedModule:
 | |
|               {name: 'ChildModule2', filePath: path.resolve(testSupport.basePath, 'src/child2.ts')},
 | |
|           route: './child2#ChildModule2'
 | |
|         },
 | |
|       ]);
 | |
|     });
 | |
| 
 | |
|     it('should list lazyRoutes pointing to a default export', () => {
 | |
|       testSupport.writeFiles({
 | |
|         'src/main.ts': `
 | |
|           import {NgModule} from '@angular/core';
 | |
|           import {RouterModule} from '@angular/router';
 | |
| 
 | |
|           @NgModule({
 | |
|             imports: [RouterModule.forRoot([{loadChildren: './child'}])]
 | |
|           })
 | |
|           export class MainModule {}
 | |
|         `,
 | |
|         'src/child.ts': `
 | |
|           import {NgModule} from '@angular/core';
 | |
| 
 | |
|           @NgModule()
 | |
|           export default class ChildModule {}
 | |
|         `,
 | |
|       });
 | |
|       const {program, options} = createProgram(['src/main.ts']);
 | |
|       expect(normalizeRoutes(program.listLazyRoutes('src/main#MainModule'))).toEqual([
 | |
|         {
 | |
|           module: {name: 'MainModule', filePath: path.resolve(testSupport.basePath, 'src/main.ts')},
 | |
|           referencedModule:
 | |
|               {name: undefined, filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
 | |
|           route: './child'
 | |
|         },
 | |
|       ]);
 | |
|     });
 | |
| 
 | |
|     it('should list lazyRoutes from imported modules', () => {
 | |
|       testSupport.writeFiles({
 | |
|         'src/main.ts': `
 | |
|           import {NgModule} from '@angular/core';
 | |
|           import {RouterModule} from '@angular/router';
 | |
|           import {NestedMainModule} from './nested/main';
 | |
| 
 | |
|           @NgModule({
 | |
|             imports: [
 | |
|               RouterModule.forRoot([{loadChildren: './child#ChildModule'}]),
 | |
|               NestedMainModule,
 | |
|             ]
 | |
|           })
 | |
|           export class MainModule {}
 | |
|         `,
 | |
|         'src/child.ts': `
 | |
|           import {NgModule} from '@angular/core';
 | |
| 
 | |
|           @NgModule()
 | |
|           export class ChildModule {}
 | |
|         `,
 | |
|         'src/nested/main.ts': `
 | |
|           import {NgModule} from '@angular/core';
 | |
|           import {RouterModule} from '@angular/router';
 | |
| 
 | |
|           @NgModule({
 | |
|             imports: [RouterModule.forChild([{loadChildren: './child#NestedChildModule'}])]
 | |
|           })
 | |
|           export class NestedMainModule {}
 | |
|         `,
 | |
|         'src/nested/child.ts': `
 | |
|           import {NgModule} from '@angular/core';
 | |
| 
 | |
|           @NgModule()
 | |
|           export class NestedChildModule {}
 | |
|         `,
 | |
|       });
 | |
|       const {program, options} = createProgram(['src/main.ts']);
 | |
|       expect(normalizeRoutes(program.listLazyRoutes('src/main#MainModule'))).toEqual([
 | |
|         {
 | |
|           module: {
 | |
|             name: 'NestedMainModule',
 | |
|             filePath: path.resolve(testSupport.basePath, 'src/nested/main.ts')
 | |
|           },
 | |
|           referencedModule: {
 | |
|             name: 'NestedChildModule',
 | |
|             filePath: path.resolve(testSupport.basePath, 'src/nested/child.ts')
 | |
|           },
 | |
|           route: './child#NestedChildModule'
 | |
|         },
 | |
|         {
 | |
|           module: {name: 'MainModule', filePath: path.resolve(testSupport.basePath, 'src/main.ts')},
 | |
|           referencedModule:
 | |
|               {name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
 | |
|           route: './child#ChildModule'
 | |
|         },
 | |
|       ]);
 | |
|     });
 | |
| 
 | |
|     it('should dedupe lazyRoutes given an entryRoute', () => {
 | |
|       writeSomeRoutes();
 | |
|       testSupport.writeFiles({
 | |
|         'src/index.ts': `
 | |
|           import {NgModule} from '@angular/core';
 | |
|           import {RouterModule} from '@angular/router';
 | |
| 
 | |
|           @NgModule({
 | |
|             imports: [
 | |
|               RouterModule.forRoot([{loadChildren: './main#MainModule'}]),
 | |
|               RouterModule.forRoot([{loadChildren: './child#ChildModule'}]),
 | |
|             ]
 | |
|           })
 | |
|           export class MainModule {}
 | |
|         `,
 | |
|       });
 | |
|       const {program, options} = createProgram(['src/index.ts']);
 | |
|       expectNoDiagnosticsInProgram(options, program);
 | |
|       expect(normalizeRoutes(program.listLazyRoutes('src/main#MainModule'))).toEqual([
 | |
|         {
 | |
|           module: {name: 'MainModule', filePath: path.resolve(testSupport.basePath, 'src/main.ts')},
 | |
|           referencedModule:
 | |
|               {name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
 | |
|           route: './child#ChildModule'
 | |
|         },
 | |
|         {
 | |
|           module:
 | |
|               {name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
 | |
|           referencedModule:
 | |
|               {name: 'ChildModule2', filePath: path.resolve(testSupport.basePath, 'src/child2.ts')},
 | |
|           route: './child2#ChildModule2'
 | |
|         },
 | |
|       ]);
 | |
|     });
 | |
| 
 | |
|     it('should list lazyRoutes given an entryRoute even with static errors', () => {
 | |
|       testSupport.writeFiles({
 | |
|         'src/main.ts': `
 | |
|           import {NgModule, Component} from '@angular/core';
 | |
|           import {RouterModule} from '@angular/router';
 | |
| 
 | |
|           @Component({
 | |
|             selector: 'url-comp',
 | |
|             // Non existent external template
 | |
|             templateUrl: 'non-existent.html',
 | |
|           })
 | |
|           export class ErrorComp {}
 | |
| 
 | |
|           @Component({
 | |
|             selector: 'err-comp',
 | |
|             // Error in template
 | |
|             template: '<input/>{{',
 | |
|           })
 | |
|           export class ErrorComp2 {}
 | |
| 
 | |
|           // Component with metadata errors.
 | |
|           @Component(() => {if (1==1) return null as any;})
 | |
|           export class ErrorComp3 {}
 | |
| 
 | |
|           // Unused component
 | |
|           @Component({
 | |
|             selector: 'unused-comp',
 | |
|             template: ''
 | |
|           })
 | |
|           export class UnusedComp {}
 | |
| 
 | |
|           @NgModule({
 | |
|             declarations: [ErrorComp, ErrorComp2, ErrorComp3, NonExistentComp],
 | |
|             imports: [RouterModule.forRoot([{loadChildren: './child#ChildModule'}])]
 | |
|           })
 | |
|           export class MainModule {}
 | |
| 
 | |
|           @NgModule({
 | |
|             // Component used in 2 NgModules
 | |
|             declarations: [ErrorComp],
 | |
|           })
 | |
|           export class Mod2 {}
 | |
|         `,
 | |
|         'src/child.ts': `
 | |
|           import {NgModule} from '@angular/core';
 | |
| 
 | |
|           @NgModule()
 | |
|           export class ChildModule {}
 | |
|         `,
 | |
|       });
 | |
|       const program = createProgram(['src/main.ts'], {collectAllErrors: true}).program;
 | |
|       expect(normalizeRoutes(program.listLazyRoutes('src/main#MainModule'))).toEqual([{
 | |
|         module: {name: 'MainModule', filePath: path.resolve(testSupport.basePath, 'src/main.ts')},
 | |
|         referencedModule:
 | |
|             {name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
 | |
|         route: './child#ChildModule'
 | |
|       }]);
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   it('should report errors for ts and ng errors on emit with noEmitOnError=true', () => {
 | |
|     testSupport.writeFiles({
 | |
|       'src/main.ts': `
 | |
|         import {Component, NgModule} from '@angular/core';
 | |
| 
 | |
|         // Ts error
 | |
|         let x: string = 1;
 | |
| 
 | |
|         // Ng error
 | |
|         @Component({selector: 'comp', templateUrl: './main.html'})
 | |
|         export class MyComp {}
 | |
| 
 | |
|         @NgModule({declarations: [MyComp]})
 | |
|         export class MyModule {}
 | |
|         `,
 | |
|       'src/main.html': '{{nonExistent}}'
 | |
|     });
 | |
|     const options = testSupport.createCompilerOptions({noEmitOnError: true});
 | |
|     const host = ng.createCompilerHost({options});
 | |
|     const program1 = ng.createProgram(
 | |
|         {rootNames: [path.resolve(testSupport.basePath, 'src/main.ts')], options, host});
 | |
|     const errorDiags =
 | |
|         program1.emit().diagnostics.filter(d => d.category === ts.DiagnosticCategory.Error);
 | |
|     expect(formatDiagnostics(errorDiags))
 | |
|         .toContain(`src/main.ts(5,13): error TS2322: Type '1' is not assignable to type 'string'.`);
 | |
|     expect(formatDiagnostics(errorDiags))
 | |
|         .toContain(
 | |
|             `src/main.html(1,1): error TS100: Property 'nonExistent' does not exist on type 'MyComp'.`);
 | |
|   });
 | |
| 
 | |
|   it('should not report emit errors with noEmitOnError=false', () => {
 | |
|     testSupport.writeFiles({
 | |
|       'src/main.ts': `
 | |
|         @NgModule()
 | |
|       `
 | |
|     });
 | |
|     const options = testSupport.createCompilerOptions({noEmitOnError: false});
 | |
|     const host = ng.createCompilerHost({options});
 | |
|     const program1 = ng.createProgram(
 | |
|         {rootNames: [path.resolve(testSupport.basePath, 'src/main.ts')], options, host});
 | |
|     expect(program1.emit().diagnostics.length).toBe(0);
 | |
|   });
 | |
| 
 | |
|   describe('errors', () => {
 | |
|     const fileWithStructuralError = `
 | |
|       import {NgModule} from '@angular/core';
 | |
| 
 | |
|       @NgModule(() => (1===1 ? null as any : null as any))
 | |
|       export class MyModule {}
 | |
|     `;
 | |
|     const fileWithGoodContent = `
 | |
|       import {NgModule} from '@angular/core';
 | |
| 
 | |
|       @NgModule()
 | |
|       export class MyModule {}
 | |
|     `;
 | |
| 
 | |
|     it('should not throw on structural errors but collect them', () => {
 | |
|       testSupport.write('src/index.ts', fileWithStructuralError);
 | |
| 
 | |
|       const options = testSupport.createCompilerOptions();
 | |
|       const host = ng.createCompilerHost({options});
 | |
|       const program = ng.createProgram(
 | |
|           {rootNames: [path.resolve(testSupport.basePath, 'src/index.ts')], options, host});
 | |
| 
 | |
|       const structuralErrors = program.getNgStructuralDiagnostics();
 | |
|       expect(structuralErrors.length).toBe(1);
 | |
|       expect(structuralErrors[0].messageText).toContain('Function expressions are not supported');
 | |
|     });
 | |
| 
 | |
|     it('should not throw on structural errors but collect them (loadNgStructureAsync)', (done) => {
 | |
|       testSupport.write('src/index.ts', fileWithStructuralError);
 | |
| 
 | |
|       const options = testSupport.createCompilerOptions();
 | |
|       const host = ng.createCompilerHost({options});
 | |
|       const program = ng.createProgram(
 | |
|           {rootNames: [path.resolve(testSupport.basePath, 'src/index.ts')], options, host});
 | |
|       program.loadNgStructureAsync().then(() => {
 | |
|         const structuralErrors = program.getNgStructuralDiagnostics();
 | |
|         expect(structuralErrors.length).toBe(1);
 | |
|         expect(structuralErrors[0].messageText).toContain('Function expressions are not supported');
 | |
|         done();
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('should include non-formatted errors (e.g. invalid templateUrl)', () => {
 | |
|       testSupport.write('src/index.ts', `
 | |
|         import {Component, NgModule} from '@angular/core';
 | |
| 
 | |
|         @Component({
 | |
|           selector: 'my-component',
 | |
|           templateUrl: 'template.html',   // invalid template url
 | |
|         })
 | |
|         export class MyComponent {}
 | |
| 
 | |
|         @NgModule({
 | |
|           declarations: [MyComponent]
 | |
|         })
 | |
|         export class MyModule {}
 | |
|       `);
 | |
| 
 | |
|       const options = testSupport.createCompilerOptions();
 | |
|       const host = ng.createCompilerHost({options});
 | |
|       const program = ng.createProgram({
 | |
|         rootNames: [path.resolve(testSupport.basePath, 'src/index.ts')],
 | |
|         options,
 | |
|         host,
 | |
|       });
 | |
| 
 | |
|       const structuralErrors = program.getNgStructuralDiagnostics();
 | |
|       expect(structuralErrors.length).toBe(1);
 | |
|       expect(structuralErrors[0].messageText).toContain('Couldn\'t resolve resource template.html');
 | |
|     });
 | |
| 
 | |
|     it('should be able report structural errors with noResolve:true and generateCodeForLibraries:false ' +
 | |
|            'even if getSourceFile throws for non existent files',
 | |
|        () => {
 | |
|          testSupport.write('src/index.ts', fileWithGoodContent);
 | |
| 
 | |
|          // compile angular and produce .ngsummary.json / ngfactory.d.ts files
 | |
|          compile();
 | |
| 
 | |
|          testSupport.write('src/ok.ts', fileWithGoodContent);
 | |
|          testSupport.write('src/error.ts', fileWithStructuralError);
 | |
| 
 | |
|          // Make sure the ok.ts file is before the error.ts file,
 | |
|          // so we added a .ngfactory.ts file for it.
 | |
|          const allRootNames = resolveFiles(
 | |
|              ['src/ok.ts', 'src/error.ts'].map(fn => path.resolve(testSupport.basePath, fn)));
 | |
| 
 | |
|          const options = testSupport.createCompilerOptions({
 | |
|            noResolve: true,
 | |
|            generateCodeForLibraries: false,
 | |
|          });
 | |
|          const host = ng.createCompilerHost({options});
 | |
|          const originalGetSourceFile = host.getSourceFile;
 | |
|          host.getSourceFile =
 | |
|              (fileName: string, languageVersion: ts.ScriptTarget,
 | |
|               onError?: ((message: string) => void) | undefined): ts.SourceFile => {
 | |
|                // We should never try to load .ngfactory.ts files
 | |
|                if (fileName.match(/\.ngfactory\.ts$/)) {
 | |
|                  throw new Error(`Non existent ngfactory file: ` + fileName);
 | |
|                }
 | |
|                return originalGetSourceFile.call(host, fileName, languageVersion, onError);
 | |
|              };
 | |
|          const program = ng.createProgram({rootNames: allRootNames, options, host});
 | |
|          const structuralErrors = program.getNgStructuralDiagnostics();
 | |
|          expect(structuralErrors.length).toBe(1);
 | |
|          expect(structuralErrors[0].messageText)
 | |
|              .toContain('Function expressions are not supported');
 | |
|        });
 | |
|   });
 | |
| });
 |