Make the emitter no longer depend on the Program.

This breaks layering.  Also, it means the emitter depends on too large a surface area.
Now the emitter declares exactly what it needs, and only gets that.
This commit is contained in:
Cyrus Najmabadi 2014-12-16 13:52:47 -08:00
parent 96c3c90d9a
commit b665323d45
9 changed files with 73 additions and 65 deletions

View File

@ -23,7 +23,6 @@ module ts {
var emitResolver = createResolver();
var checker: TypeChecker = {
getProgram: () => program,
getNodeCount: () => sum(program.getSourceFiles(), "nodeCount"),
getIdentifierCount: () => sum(program.getSourceFiles(), "identifierCount"),
getSymbolCount: () => sum(program.getSourceFiles(), "symbolCount"),
@ -9549,7 +9548,6 @@ module ts {
function createResolver(): EmitResolver {
return {
getProgram: () => program,
getLocalNameOfContainer,
getExpressionNamePrefix,
getExportAssignmentName,

View File

@ -312,17 +312,17 @@ module ts {
};
}
function getSourceFilePathInNewDir(sourceFile: SourceFile, program: Program, newDirPath: string) {
var compilerHost = program.getCompilerHost();
function getSourceFilePathInNewDir(sourceFile: SourceFile, host: EmitHost, newDirPath: string) {
var compilerHost = host.getCompilerHost();
var sourceFilePath = getNormalizedAbsolutePath(sourceFile.filename, compilerHost.getCurrentDirectory());
sourceFilePath = sourceFilePath.replace(program.getCommonSourceDirectory(), "");
sourceFilePath = sourceFilePath.replace(host.getCommonSourceDirectory(), "");
return combinePaths(newDirPath, sourceFilePath);
}
function getOwnEmitOutputFilePath(sourceFile: SourceFile, program: Program, extension: string){
var compilerOptions = program.getCompilerOptions();
function getOwnEmitOutputFilePath(sourceFile: SourceFile, host: EmitHost, extension: string){
var compilerOptions = host.getCompilerOptions();
if (compilerOptions.outDir) {
var emitOutputFilePathWithoutExtension = removeFileExtension(getSourceFilePathInNewDir(sourceFile, program, compilerOptions.outDir));
var emitOutputFilePathWithoutExtension = removeFileExtension(getSourceFilePathInNewDir(sourceFile, host, compilerOptions.outDir));
}
else {
var emitOutputFilePathWithoutExtension = removeFileExtension(sourceFile.filename);
@ -337,10 +337,10 @@ module ts {
});
}
function emitDeclarations(program: Program, resolver: EmitResolver, diagnostics: Diagnostic[], jsFilePath: string, root?: SourceFile): DeclarationEmit {
var newLine = program.getCompilerHost().getNewLine();
var compilerOptions = program.getCompilerOptions();
var compilerHost = program.getCompilerHost();
function emitDeclarations(host: EmitHost, resolver: EmitResolver, diagnostics: Diagnostic[], jsFilePath: string, root?: SourceFile): DeclarationEmit {
var newLine = host.getCompilerHost().getNewLine();
var compilerOptions = host.getCompilerOptions();
var compilerHost = host.getCompilerHost();
var write: (s: string) => void;
var writeLine: () => void;
@ -1396,8 +1396,8 @@ module ts {
var declFileName = referencedFile.flags & NodeFlags.DeclarationFile
? referencedFile.filename // Declaration file, use declaration file name
: shouldEmitToOwnFile(referencedFile, compilerOptions)
? getOwnEmitOutputFilePath(referencedFile, program, ".d.ts") // Own output file so get the .d.ts file
: removeFileExtension(compilerOptions.out) + ".d.ts";// Global out file
? getOwnEmitOutputFilePath(referencedFile, host, ".d.ts") // Own output file so get the .d.ts file
: removeFileExtension(compilerOptions.out) + ".d.ts";// Global out file
declFileName = getRelativePathToDirectoryOrUrl(
getDirectoryPath(normalizeSlashes(jsFilePath)),
@ -1414,7 +1414,7 @@ module ts {
if (!compilerOptions.noResolve) {
var addedGlobalFileReference = false;
forEach(root.referencedFiles, fileReference => {
var referencedFile = tryResolveScriptReference(program, root, fileReference);
var referencedFile = tryResolveScriptReference(host, root, fileReference);
// All the references that are not going to be part of same file
if (referencedFile && ((referencedFile.flags & NodeFlags.DeclarationFile) || // This is a declare file reference
@ -1434,12 +1434,12 @@ module ts {
else {
// Emit references corresponding to this file
var emittedReferencedFiles: SourceFile[] = [];
forEach(program.getSourceFiles(), sourceFile => {
forEach(host.getSourceFiles(), sourceFile => {
if (!isExternalModuleOrDeclarationFile(sourceFile)) {
// Check what references need to be added
if (!compilerOptions.noResolve) {
forEach(sourceFile.referencedFiles, fileReference => {
var referencedFile = tryResolveScriptReference(program, sourceFile, fileReference);
var referencedFile = tryResolveScriptReference(host, sourceFile, fileReference);
// If the reference file is a declaration file or an external module, emit that reference
if (referencedFile && (isExternalModuleOrDeclarationFile(referencedFile) &&
@ -1472,13 +1472,13 @@ module ts {
}
// targetSourceFile is when users only want one file in entire project to be emitted. This is used in compilerOnSave feature
export function emitFiles(resolver: EmitResolver, targetSourceFile?: SourceFile): EmitResult {
var program = resolver.getProgram();
var compilerHost = program.getCompilerHost();
var compilerOptions = program.getCompilerOptions();
export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile?: SourceFile): EmitResult {
// var program = resolver.getProgram();
var compilerHost = host.getCompilerHost();
var compilerOptions = host.getCompilerOptions();
var sourceMapDataList: SourceMapData[] = compilerOptions.sourceMap ? [] : undefined;
var diagnostics: Diagnostic[] = [];
var newLine = program.getCompilerHost().getNewLine();
var newLine = compilerHost.getNewLine();
function emitJavaScript(jsFilePath: string, root?: SourceFile) {
var writer = createTextWriter(newLine);
@ -1700,7 +1700,7 @@ module ts {
// Add the file to tsFilePaths
// If sourceroot option: Use the relative path corresponding to the common directory path
// otherwise source locations relative to map file location
var sourcesDirectoryPath = compilerOptions.sourceRoot ? program.getCommonSourceDirectory() : sourceMapDir;
var sourcesDirectoryPath = compilerOptions.sourceRoot ? host.getCommonSourceDirectory() : sourceMapDir;
sourceMapData.sourceMapSources.push(getRelativePathToDirectoryOrUrl(sourcesDirectoryPath,
node.filename,
@ -1840,12 +1840,12 @@ module ts {
if (root) { // emitting single module file
// For modules or multiple emit files the mapRoot will have directory structure like the sources
// So if src\a.ts and src\lib\b.ts are compiled together user would be moving the maps into mapRoot\a.js.map and mapRoot\lib\b.js.map
sourceMapDir = getDirectoryPath(getSourceFilePathInNewDir(root, program, sourceMapDir));
sourceMapDir = getDirectoryPath(getSourceFilePathInNewDir(root, host, sourceMapDir));
}
if (!isRootedDiskPath(sourceMapDir) && !isUrl(sourceMapDir)) {
// The relative paths are relative to the common directory
sourceMapDir = combinePaths(program.getCommonSourceDirectory(), sourceMapDir);
sourceMapDir = combinePaths(host.getCommonSourceDirectory(), sourceMapDir);
sourceMapData.jsSourceMappingURL = getRelativePathToDirectoryOrUrl(
getDirectoryPath(normalizePath(jsFilePath)), // get the relative sourceMapDir path based on jsFilePath
combinePaths(sourceMapDir, sourceMapData.jsSourceMappingURL), // this is where user expects to see sourceMap
@ -4101,7 +4101,7 @@ module ts {
emit(root);
}
else {
forEach(program.getSourceFiles(), sourceFile => {
forEach(host.getSourceFiles(), sourceFile => {
if (!isExternalModuleOrDeclarationFile(sourceFile)) {
emit(sourceFile);
}
@ -4113,7 +4113,7 @@ module ts {
}
function writeDeclarationFile(jsFilePath: string, sourceFile: SourceFile) {
var emitDeclarationResult = emitDeclarations(program, resolver, diagnostics, jsFilePath, sourceFile);
var emitDeclarationResult = emitDeclarations(host, resolver, diagnostics, jsFilePath, sourceFile);
// TODO(shkamat): Should we not write any declaration file if any of them can produce error,
// or should we just not write this file like we are doing now
if (!emitDeclarationResult.reportedDeclarationError) {
@ -4140,9 +4140,9 @@ module ts {
hasSemanticErrors = resolver.hasSemanticErrors();
isEmitBlocked = resolver.isEmitBlocked();
forEach(program.getSourceFiles(), sourceFile => {
forEach(host.getSourceFiles(), sourceFile => {
if (shouldEmitToOwnFile(sourceFile, compilerOptions)) {
var jsFilePath = getOwnEmitOutputFilePath(sourceFile, program, ".js");
var jsFilePath = getOwnEmitOutputFilePath(sourceFile, host, ".js");
emitFile(jsFilePath, sourceFile);
}
});
@ -4158,13 +4158,13 @@ module ts {
hasSemanticErrors = resolver.hasSemanticErrors(targetSourceFile);
isEmitBlocked = resolver.isEmitBlocked(targetSourceFile);
var jsFilePath = getOwnEmitOutputFilePath(targetSourceFile, program, ".js");
var jsFilePath = getOwnEmitOutputFilePath(targetSourceFile, host, ".js");
emitFile(jsFilePath, targetSourceFile);
}
else if (!isDeclarationFile(targetSourceFile) && compilerOptions.out) {
// Otherwise, if --out is specified and targetSourceFile is not a declaration file,
// Emit all, non-external-module file, into one single output file
forEach(program.getSourceFiles(), sourceFile => {
forEach(host.getSourceFiles(), sourceFile => {
if (!shouldEmitToOwnFile(sourceFile, compilerOptions)) {
hasSemanticErrors = hasSemanticErrors || resolver.hasSemanticErrors(sourceFile);
isEmitBlocked = isEmitBlocked || resolver.isEmitBlocked(sourceFile);

View File

@ -92,15 +92,6 @@ module ts {
var diagnosticsProducingTypeChecker: TypeChecker;
var noDiagnosticsTypeChecker: TypeChecker;
function getTypeChecker(produceDiagnostics: boolean) {
if (produceDiagnostics) {
return diagnosticsProducingTypeChecker || (diagnosticsProducingTypeChecker = createTypeChecker(program, produceDiagnostics));
}
else {
return noDiagnosticsTypeChecker || (noDiagnosticsTypeChecker = createTypeChecker(program, produceDiagnostics));
}
}
program = {
getSourceFile: getSourceFile,
getSourceFiles: () => files,
@ -115,6 +106,15 @@ module ts {
};
return program;
function getTypeChecker(produceDiagnostics: boolean) {
if (produceDiagnostics) {
return diagnosticsProducingTypeChecker || (diagnosticsProducingTypeChecker = createTypeChecker(program, produceDiagnostics));
}
else {
return noDiagnosticsTypeChecker || (noDiagnosticsTypeChecker = createTypeChecker(program, produceDiagnostics));
}
}
function getDeclarationDiagnostics(targetSourceFile: SourceFile): Diagnostic[]{
var fullTypeChecker = getTypeChecker(/*produceDiagnostics:*/true);
fullTypeChecker.getDiagnostics(targetSourceFile);
@ -124,7 +124,7 @@ module ts {
function invokeEmitter(targetSourceFile?: SourceFile) {
var resolver = getTypeChecker(/*produceDiagnostics:*/true).getEmitResolver();
return emitFiles(resolver, targetSourceFile);
return emitFiles(resolver, program, targetSourceFile);
}
function getSourceFile(filename: string) {

View File

@ -246,7 +246,6 @@ module ts {
EnumMember,
// Top-level nodes
SourceFile,
Program,
// Synthesized list
SyntaxList,
@ -968,7 +967,6 @@ module ts {
}
export interface TypeChecker {
getProgram(): Program;
getEmitResolver(): EmitResolver;
getDiagnostics(sourceFile?: SourceFile): Diagnostic[];
getGlobalDiagnostics(): Diagnostic[];
@ -1076,8 +1074,15 @@ module ts {
errorModuleName?: string // If the symbol is not visible from module, module's name
}
export interface EmitHost {
getSourceFile(filename: string): SourceFile;
getSourceFiles(): SourceFile[];
getCompilerHost(): CompilerHost;
getCompilerOptions(): CompilerOptions;
getCommonSourceDirectory(): string;
}
export interface EmitResolver {
getProgram(): Program;
getLocalNameOfContainer(container: ModuleDeclaration | EnumDeclaration): string;
getExpressionNamePrefix(node: Identifier): string;
getExportAssignmentName(node: SourceFile): string;

View File

@ -636,11 +636,11 @@ module ts {
return undefined;
}
export function tryResolveScriptReference(program: Program, sourceFile: SourceFile, reference: FileReference) {
if (!program.getCompilerOptions().noResolve) {
export function tryResolveScriptReference(host: EmitHost, sourceFile: SourceFile, reference: FileReference) {
if (!host.getCompilerOptions().noResolve) {
var referenceFileName = isRootedDiskPath(reference.filename) ? reference.filename : combinePaths(getDirectoryPath(sourceFile.filename), reference.filename);
referenceFileName = getNormalizedAbsolutePath(referenceFileName, program.getCompilerHost().getCurrentDirectory());
return program.getSourceFile(referenceFileName);
referenceFileName = getNormalizedAbsolutePath(referenceFileName, host.getCompilerHost().getCurrentDirectory());
return host.getSourceFile(referenceFileName);
}
}

View File

@ -53,7 +53,7 @@ class CompilerBaselineRunner extends RunnerBase {
var rootDir: string;
var result: Harness.Compiler.CompilerResult;
var checker: ts.TypeChecker;
var program: ts.Program;
var options: ts.CompilerOptions;
// equivalent to the files that will be passed on the command line
var toBeCompiled: { unitName: string; content: string }[];
@ -97,10 +97,10 @@ class CompilerBaselineRunner extends RunnerBase {
});
}
options = harnessCompiler.compileFiles(toBeCompiled, otherFiles, function (compileResult, _checker) {
options = harnessCompiler.compileFiles(toBeCompiled, otherFiles, function (compileResult, _program) {
result = compileResult;
// The checker will be used by typeWriter
checker = _checker;
// The program will be used by typeWriter
program = _program;
}, function (settings) {
harnessCompiler.setCompilerSettings(tcSettings);
});
@ -138,7 +138,7 @@ class CompilerBaselineRunner extends RunnerBase {
lastUnit = undefined;
rootDir = undefined;
result = undefined;
checker = undefined;
program = undefined;
options = undefined;
toBeCompiled = undefined;
otherFiles = undefined;
@ -267,10 +267,10 @@ class CompilerBaselineRunner extends RunnerBase {
// NEWTODO: Type baselines
if (result.errors.length === 0) {
Harness.Baseline.runBaseline('Correct expression types for ' + fileName, justName.replace(/\.ts/, '.types'), () => {
var allFiles = toBeCompiled.concat(otherFiles).filter(file => !!checker.getProgram().getSourceFile(file.unitName));
var allFiles = toBeCompiled.concat(otherFiles).filter(file => !!program.getSourceFile(file.unitName));
var typeLines: string[] = [];
var typeMap: { [fileName: string]: { [lineNum: number]: string[]; } } = {};
var walker = new TypeWriterWalker(checker);
var walker = new TypeWriterWalker(program);
allFiles.forEach(file => {
var codeLines = file.content.split('\n');
walker.getTypes(file.unitName).forEach(result => {

View File

@ -885,7 +885,7 @@ module Harness {
public compileFiles(inputFiles: { unitName: string; content: string }[],
otherFiles: { unitName: string; content: string }[],
onComplete: (result: CompilerResult, checker: ts.TypeChecker) => void,
onComplete: (result: CompilerResult, program: ts.Program) => void,
settingsCallback?: (settings: ts.CompilerOptions) => void,
options?: ts.CompilerOptions) {
@ -1062,7 +1062,7 @@ module Harness {
this.lastErrors = errors;
var result = new CompilerResult(fileOutputs, errors, program, ts.sys.getCurrentDirectory(), emitResult ? emitResult.sourceMaps : undefined);
onComplete(result, checker);
onComplete(result, program);
// reset what newline means in case the last test changed it
ts.sys.newLine = '\r\n';

View File

@ -29,7 +29,7 @@ class Test262BaselineRunner extends RunnerBase {
filename: string;
compilerResult: Harness.Compiler.CompilerResult;
inputFiles: { unitName: string; content: string }[];
checker: ts.TypeChecker;
program: ts.Program;
};
before(() => {
@ -46,12 +46,12 @@ class Test262BaselineRunner extends RunnerBase {
filename: testFilename,
inputFiles: inputFiles,
compilerResult: undefined,
checker: undefined,
program: undefined,
};
Harness.Compiler.getCompiler().compileFiles([Test262BaselineRunner.helperFile].concat(inputFiles), /*otherFiles*/ [], (compilerResult, checker) => {
Harness.Compiler.getCompiler().compileFiles([Test262BaselineRunner.helperFile].concat(inputFiles), /*otherFiles*/ [], (compilerResult, program) => {
testState.compilerResult = compilerResult;
testState.checker = checker;
testState.program = program;
}, /*settingsCallback*/ undefined, Test262BaselineRunner.options);
});
@ -78,13 +78,13 @@ class Test262BaselineRunner extends RunnerBase {
});
it('satisfies invariants', () => {
var sourceFile = testState.checker.getProgram().getSourceFile(Test262BaselineRunner.getTestFilePath(testState.filename));
var sourceFile = testState.program.getSourceFile(Test262BaselineRunner.getTestFilePath(testState.filename));
Utils.assertInvariants(sourceFile, /*parent:*/ undefined);
});
it('has the expected AST',() => {
Harness.Baseline.runBaseline('has the expected AST', testState.filename + '.AST.txt',() => {
var sourceFile = testState.checker.getProgram().getSourceFile(Test262BaselineRunner.getTestFilePath(testState.filename));
var sourceFile = testState.program.getSourceFile(Test262BaselineRunner.getTestFilePath(testState.filename));
return Utils.sourceFileToJSON(sourceFile);
}, false, Test262BaselineRunner.baselineOptions);
});

View File

@ -10,11 +10,16 @@ class TypeWriterWalker {
results: TypeWriterResult[];
currentSourceFile: ts.SourceFile;
constructor(public checker: ts.TypeChecker) {
private checker: ts.TypeChecker;
constructor(private program: ts.Program) {
// Consider getting both the diagnostics checker and the non-diagnostics checker to verify
// they are consistent.
this.checker = program.getTypeChecker(/*produceDiagnostics:*/ true);
}
public getTypes(fileName: string): TypeWriterResult[] {
var sourceFile = this.checker.getProgram().getSourceFile(fileName);
var sourceFile = this.program.getSourceFile(fileName);
this.currentSourceFile = sourceFile;
this.results = [];
this.visitNode(sourceFile);