diff --git a/packages/compiler-cli/src/transformers/program.ts b/packages/compiler-cli/src/transformers/program.ts index 54cc9ed527..5d82e89017 100644 --- a/packages/compiler-cli/src/transformers/program.ts +++ b/packages/compiler-cli/src/transformers/program.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {AotCompiler, AotCompilerHost, AotCompilerOptions, EmitterVisitorContext, GeneratedFile, MessageBundle, NgAnalyzedFile, NgAnalyzedModules, ParseSourceSpan, Serializer, StubEmitFlags, TypeScriptEmitter, Xliff, Xliff2, Xmb, core, createAotCompiler, getParseErrors, isSyntaxError} from '@angular/compiler'; +import {AotCompiler, AotCompilerHost, AotCompilerOptions, EmitterVisitorContext, GeneratedFile, MessageBundle, NgAnalyzedFile, NgAnalyzedModules, ParseSourceSpan, Serializer, TypeScriptEmitter, Xliff, Xliff2, Xmb, core, createAotCompiler, getParseErrors, isSyntaxError} from '@angular/compiler'; import * as fs from 'fs'; import * as path from 'path'; import * as ts from 'typescript'; @@ -63,7 +63,6 @@ class AngularCompilerProgram implements Program { ({content, fileName}) => this.summariesFromPreviousCompilations.set(fileName, content)); } - this.rootNames = rootNames = rootNames.filter(r => !GENERATED_FILES.test(r)); if (options.flatModuleOutFile) { const {host: bundleHost, indexName, errors} = createBundleIndexHost(options, rootNames, host); if (errors) { @@ -133,14 +132,15 @@ class AngularCompilerProgram implements Program { if (this._analyzedModules) { throw new Error('Angular structure already loaded'); } - const {tmpProgram, analyzedFiles, hostAdapter} = this._createProgramWithBasicStubs(); + const {tmpProgram, analyzedFiles, hostAdapter, rootNames} = this._createProgramWithBasicStubs(); return this._compiler.loadFilesAsync(analyzedFiles) .catch(this.catchAnalysisError.bind(this)) .then(analyzedModules => { if (this._analyzedModules) { throw new Error('Angular structure loaded both synchronously and asynchronsly'); } - this._updateProgramWithTypeCheckStubs(tmpProgram, analyzedModules, hostAdapter); + this._updateProgramWithTypeCheckStubs( + tmpProgram, analyzedModules, hostAdapter, rootNames); }); } @@ -177,7 +177,7 @@ class AngularCompilerProgram implements Program { program: this.tsProgram, host: this.host, options: this.options, - writeFile: createWriteFileCallback(emitFlags, this.host, outSrcMapping), + writeFile: createWriteFileCallback(genFiles, this.host, outSrcMapping), emitOnlyDtsFiles: (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS, customTransformers: this.calculateTransforms(genFiles, customTransformers) }); @@ -291,20 +291,21 @@ class AngularCompilerProgram implements Program { if (this._analyzedModules) { return; } - const {tmpProgram, analyzedFiles, hostAdapter} = this._createProgramWithBasicStubs(); + const {tmpProgram, analyzedFiles, hostAdapter, rootNames} = this._createProgramWithBasicStubs(); let analyzedModules: NgAnalyzedModules; try { analyzedModules = this._compiler.loadFilesSync(analyzedFiles); } catch (e) { analyzedModules = this.catchAnalysisError(e); } - this._updateProgramWithTypeCheckStubs(tmpProgram, analyzedModules, hostAdapter); + this._updateProgramWithTypeCheckStubs(tmpProgram, analyzedModules, hostAdapter, rootNames); } private _createProgramWithBasicStubs(): { tmpProgram: ts.Program, analyzedFiles: NgAnalyzedFile[], - hostAdapter: TsCompilerAotCompilerTypeCheckHostAdapter + hostAdapter: TsCompilerAotCompilerTypeCheckHostAdapter, + rootNames: string[], } { if (this._analyzedModules) { throw new Error(`Internal Error: already initalized!`); @@ -330,17 +331,31 @@ class AngularCompilerProgram implements Program { this._typeCheckHost = hostAdapter; this._structuralDiagnostics = []; - const tmpProgram = ts.createProgram(this.rootNames, this.options, hostAdapter, oldTsProgram); - return {tmpProgram, analyzedFiles, hostAdapter}; + let rootNames = + this.rootNames.filter(fn => !GENERATED_FILES.test(fn) || !hostAdapter.isSourceFile(fn)); + if (this.options.noResolve) { + this.rootNames.forEach(rootName => { + const sf = + hostAdapter.getSourceFile(rootName, this.options.target || ts.ScriptTarget.Latest); + sf.referencedFiles.forEach((fileRef) => { + if (GENERATED_FILES.test(fileRef.fileName)) { + rootNames.push(fileRef.fileName); + } + }); + }); + } + + const tmpProgram = ts.createProgram(rootNames, this.options, hostAdapter, oldTsProgram); + return {tmpProgram, analyzedFiles, hostAdapter, rootNames}; } private _updateProgramWithTypeCheckStubs( tmpProgram: ts.Program, analyzedModules: NgAnalyzedModules, - hostAdapter: TsCompilerAotCompilerTypeCheckHostAdapter) { + hostAdapter: TsCompilerAotCompilerTypeCheckHostAdapter, rootNames: string[]) { this._analyzedModules = analyzedModules; const genFiles = this._compiler.emitTypeCheckStubs(analyzedModules); genFiles.forEach(gf => hostAdapter.updateGeneratedFile(gf)); - this._tsProgram = ts.createProgram(this.rootNames, this.options, hostAdapter, tmpProgram); + this._tsProgram = ts.createProgram(rootNames, this.options, hostAdapter, tmpProgram); // Note: the new ts program should be completely reusable by TypeScript as: // - we cache all the files in the hostAdapter // - new new stubs use the exactly same imports/exports as the old once (we assert that in @@ -453,17 +468,24 @@ function getAotCompilerOptions(options: CompilerOptions): AotCompilerOptions { } function createWriteFileCallback( - emitFlags: EmitFlags, host: ts.CompilerHost, + generatedFiles: GeneratedFile[], host: ts.CompilerHost, outSrcMapping: Array<{sourceFile: ts.SourceFile, outFileName: string}>) { + const genFileByFileName = new Map(); + generatedFiles.forEach(genFile => genFileByFileName.set(genFile.genFileUrl, genFile)); return (fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => { const sourceFile = sourceFiles && sourceFiles.length == 1 ? sourceFiles[0] : null; - const isGenerated = GENERATED_FILES.test(fileName); if (sourceFile) { outSrcMapping.push({outFileName: fileName, sourceFile}); } - if (isGenerated && !(emitFlags & EmitFlags.Codegen)) { - return; + const isGenerated = GENERATED_FILES.test(fileName); + if (isGenerated && sourceFile) { + // Filter out generated files for which we didn't generate code. + // This can happen as the stub caclulation is not completely exact. + const genFile = genFileByFileName.get(sourceFile.fileName); + if (!genFile || !genFile.stmts || genFile.stmts.length === 0) { + return; + } } host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles); }; diff --git a/packages/compiler-cli/test/transformers/program_spec.ts b/packages/compiler-cli/test/transformers/program_spec.ts index 0ba960e608..ee8747a903 100644 --- a/packages/compiler-cli/test/transformers/program_spec.ts +++ b/packages/compiler-cli/test/transformers/program_spec.ts @@ -208,4 +208,28 @@ describe('ng program', () => { 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 preOptions = testSupport.createCompilerOptions(); + const preHost = ts.createCompilerHost(preOptions); + // don't resolve symlinks + preHost.realpath = (f) => f; + const preProgram = + ts.createProgram([path.resolve(testSupport.basePath, 'src/main.ts')], preOptions, preHost); + const allRootNames = preProgram.getSourceFiles().map(sf => sf.fileName); + + // now do the actual test with noResolve + const options = testSupport.createCompilerOptions({noResolve: true}); + const host = ng.createCompilerHost({options}); + const program = ng.createProgram({rootNames: allRootNames, options, host}); + expectNoDiagnosticsInProgram(options, program); + program.emit(); + + testSupport.shouldExist('built/src/main.ngfactory.js'); + testSupport.shouldExist('built/src/main.ngfactory.d.ts'); + }); });