fix(compiler): support `noResolve` (#19301)

PR Close #19301
This commit is contained in:
Tobias Bosch 2017-09-20 16:27:29 -07:00 committed by Igor Minar
parent 04997c8418
commit c76da27240
2 changed files with 62 additions and 16 deletions

View File

@ -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<string, GeneratedFile>();
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);
};

View File

@ -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');
});
});