|
| 1 | +/** |
| 2 | + * @license |
| 3 | + * Copyright Google Inc. All Rights Reserved. |
| 4 | + * |
| 5 | + * Use of this source code is governed by an MIT-style license that can be |
| 6 | + * found in the LICENSE file at https://angular.io/license |
| 7 | + */ |
| 8 | + |
| 9 | +import {AotCompiler, AotCompilerHost, createAotCompiler} from '@angular/compiler'; |
| 10 | +import {async} from '@angular/core/testing'; |
| 11 | +import * as path from 'path'; |
| 12 | +import * as ts from 'typescript'; |
| 13 | + |
| 14 | +import {ReflectionCapabilities, reflector} from './private_import_core'; |
| 15 | +import {EmittingCompilerHost, MockAotCompilerHost, MockCompilerHost, MockData, settings} from './test_util'; |
| 16 | + |
| 17 | +const DTS = /\.d\.ts$/; |
| 18 | + |
| 19 | +// These are the files that contain the well known annotations. |
| 20 | +const CORE_FILES = [ |
| 21 | + '@angular/core/src/metadata.ts', '@angular/core/src/di/metadata.ts', |
| 22 | + '@angular/core/src/di/injection_token.ts', '@angular/core/src/animation/metadata.ts', |
| 23 | + '@angular/core/src/di/provider.ts', '@angular/core/src/linker/view.ts' |
| 24 | +]; |
| 25 | + |
| 26 | +describe('compiler', () => { |
| 27 | + let angularFiles: Map<string, string>; |
| 28 | + |
| 29 | + beforeAll(() => { |
| 30 | + const emittingHost = new EmittingCompilerHost(CORE_FILES); |
| 31 | + const emittingProgram = ts.createProgram(emittingHost.scripts, settings, emittingHost); |
| 32 | + emittingProgram.emit(); |
| 33 | + angularFiles = emittingHost.written; |
| 34 | + }); |
| 35 | + |
| 36 | + describe('Quickstart', () => { |
| 37 | + let host: MockCompilerHost; |
| 38 | + let aotHost: MockAotCompilerHost; |
| 39 | + |
| 40 | + beforeEach(() => { |
| 41 | + host = new MockCompilerHost(QUICKSTART, FILES, angularFiles); |
| 42 | + aotHost = new MockAotCompilerHost(host); |
| 43 | + }); |
| 44 | + |
| 45 | + // Restore reflector since AoT compiler will update it with a new static reflector |
| 46 | + afterEach(() => { reflector.updateCapabilities(new ReflectionCapabilities()); }); |
| 47 | + |
| 48 | + it('should compile', |
| 49 | + async(() => compile(host, aotHost, expectNoDiagnostics).then(generatedFiles => { |
| 50 | + expect(generatedFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl))) |
| 51 | + .not.toBeUndefined(); |
| 52 | + expect(generatedFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl))) |
| 53 | + .not.toBeUndefined(); |
| 54 | + }))); |
| 55 | + |
| 56 | + it('should compile using summaries', |
| 57 | + async(() => summaryCompile(host, aotHost).then(generatedFiles => { |
| 58 | + expect(generatedFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl))) |
| 59 | + .not.toBeUndefined(); |
| 60 | + expect(generatedFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl))) |
| 61 | + .not.toBeUndefined(); |
| 62 | + }))); |
| 63 | + }); |
| 64 | +}); |
| 65 | + |
| 66 | +function expectNoDiagnostics(program: ts.Program) { |
| 67 | + function fileInfo(diagnostic: ts.Diagnostic): string { |
| 68 | + if (diagnostic.file) { |
| 69 | + return `${diagnostic.file.fileName}(${diagnostic.start}): `; |
| 70 | + } |
| 71 | + return ''; |
| 72 | + } |
| 73 | + |
| 74 | + function chars(len: number, ch: string): string { return new Array(len).fill(ch).join(''); } |
| 75 | + |
| 76 | + function lineNoOf(offset: number, text: string): number { |
| 77 | + let result = 1; |
| 78 | + for (let i = 0; i < offset; i++) { |
| 79 | + if (text[i] == '\n') result++; |
| 80 | + } |
| 81 | + return result; |
| 82 | + } |
| 83 | + |
| 84 | + function lineInfo(diagnostic: ts.Diagnostic): string { |
| 85 | + if (diagnostic.file) { |
| 86 | + const start = diagnostic.start; |
| 87 | + let end = diagnostic.start + diagnostic.length; |
| 88 | + const source = diagnostic.file.text; |
| 89 | + let lineStart = start; |
| 90 | + let lineEnd = end; |
| 91 | + while (lineStart > 0 && source[lineStart] != '\n') lineStart--; |
| 92 | + if (lineStart < start) lineStart++; |
| 93 | + while (lineEnd < source.length && source[lineEnd] != '\n') lineEnd++; |
| 94 | + let line = source.substring(lineStart, lineEnd); |
| 95 | + const lineIndex = line.indexOf('/n'); |
| 96 | + if (lineIndex > 0) { |
| 97 | + line = line.substr(0, lineIndex); |
| 98 | + end = start + lineIndex; |
| 99 | + } |
| 100 | + const lineNo = lineNoOf(start, source) + ': '; |
| 101 | + return '\n' + lineNo + line + '\n' + chars(start - lineStart + lineNo.length, ' ') + |
| 102 | + chars(end - start, '^'); |
| 103 | + } |
| 104 | + return ''; |
| 105 | + } |
| 106 | + |
| 107 | + function expectNoDiagnostics(diagnostics: ts.Diagnostic[]) { |
| 108 | + if (diagnostics && diagnostics.length) { |
| 109 | + throw new Error( |
| 110 | + 'Errors from TypeScript:\n' + |
| 111 | + diagnostics.map(d => `${fileInfo(d)}${d.messageText}${lineInfo(d)}`).join(' \n')); |
| 112 | + } |
| 113 | + } |
| 114 | + expectNoDiagnostics(program.getOptionsDiagnostics()); |
| 115 | + expectNoDiagnostics(program.getSyntacticDiagnostics()); |
| 116 | + expectNoDiagnostics(program.getSemanticDiagnostics()); |
| 117 | +} |
| 118 | + |
| 119 | +function isDTS(fileName: string): boolean { |
| 120 | + return /\.d\.ts$/.test(fileName); |
| 121 | +} |
| 122 | + |
| 123 | +function isSource(fileName: string): boolean { |
| 124 | + return /\.ts$/.test(fileName); |
| 125 | +} |
| 126 | + |
| 127 | +function isFactory(fileName: string): boolean { |
| 128 | + return /\.ngfactory\./.test(fileName); |
| 129 | +} |
| 130 | + |
| 131 | +function summaryCompile( |
| 132 | + host: MockCompilerHost, aotHost: MockAotCompilerHost, |
| 133 | + preCompile?: (program: ts.Program) => void) { |
| 134 | + // First compile the program to generate the summary files. |
| 135 | + return compile(host, aotHost).then(generatedFiles => { |
| 136 | + // Remove generated files that were not generated from a DTS file |
| 137 | + host.remove(generatedFiles.filter(f => !isDTS(f.srcFileUrl)).map(f => f.genFileUrl)); |
| 138 | + |
| 139 | + // Next compile the program shrowding metadata and only treating .ts files as source. |
| 140 | + aotHost.hideMetadata(); |
| 141 | + aotHost.tsFilesOnly(); |
| 142 | + |
| 143 | + return compile(host, aotHost); |
| 144 | + }); |
| 145 | +} |
| 146 | + |
| 147 | +function compile( |
| 148 | + host: MockCompilerHost, aotHost: AotCompilerHost, preCompile?: (program: ts.Program) => void, |
| 149 | + postCompile: (program: ts.Program) => void = expectNoDiagnostics) { |
| 150 | + const program = ts.createProgram(host.scriptNames, settings, host); |
| 151 | + if (preCompile) preCompile(program); |
| 152 | + const {compiler, reflector} = createAotCompiler(aotHost, {}); |
| 153 | + return compiler.compileAll(program.getSourceFiles().map(sf => sf.fileName)) |
| 154 | + .then(generatedFiles => { |
| 155 | + generatedFiles.forEach( |
| 156 | + file => isSource(file.genFileUrl) ? host.addScript(file.genFileUrl, file.source) : |
| 157 | + host.override(file.genFileUrl, file.source)); |
| 158 | + const newProgram = ts.createProgram(host.scriptNames, settings, host, program); |
| 159 | + if (postCompile) postCompile(newProgram); |
| 160 | + return generatedFiles; |
| 161 | + }); |
| 162 | +} |
| 163 | + |
| 164 | +const QUICKSTART = ['/quickstart/app/app.module.ts']; |
| 165 | +const FILES: MockData = { |
| 166 | + quickstart: { |
| 167 | + app: { |
| 168 | + 'app.component.ts': ` |
| 169 | + import {Component} from '@angular/core/src/metadata'; |
| 170 | +
|
| 171 | + @Component({ |
| 172 | + template: '<h1>Hello {{name}}</h1>' |
| 173 | + }) |
| 174 | + export class AppComponent { |
| 175 | + name = 'Angular'; |
| 176 | + } |
| 177 | + `, |
| 178 | + 'app.module.ts': ` |
| 179 | + import { NgModule } from '@angular/core/src/metadata'; |
| 180 | +
|
| 181 | + import { AppComponent } from './app.component'; |
| 182 | +
|
| 183 | + @NgModule({ |
| 184 | + declarations: [ AppComponent ], |
| 185 | + bootstrap: [ AppComponent ] |
| 186 | + }) |
| 187 | + export class AppModule { } |
| 188 | + ` |
| 189 | + } |
| 190 | + } |
| 191 | +}; |
0 commit comments