Remove the dependency that TypeChecker and Emitter have on Program.

Instead, these layers explicitly specify the functionality they need, and don't take in anything extra.
This commit is contained in:
Cyrus Najmabadi 2014-12-16 14:12:17 -08:00
parent b665323d45
commit f5ad79fe7a
6 changed files with 68 additions and 38 deletions

View File

@ -9,7 +9,7 @@ module ts {
/// If fullTypeCheck === true, then the typechecker should do every possible check to produce all errors
/// If fullTypeCheck === false, the typechecker can take shortcuts and skip checks that only produce errors.
/// NOTE: checks that somehow affect decisions being made during typechecking should be executed in both cases.
export function createTypeChecker(program: Program, produceDiagnostics: boolean): TypeChecker {
export function createTypeChecker(host: TypeCheckerHost, produceDiagnostics: boolean): TypeChecker {
var Symbol = objectAllocator.getSymbolConstructor();
var Type = objectAllocator.getTypeConstructor();
var Signature = objectAllocator.getSignatureConstructor();
@ -19,13 +19,13 @@ module ts {
var emptyArray: any[] = [];
var emptySymbols: SymbolTable = {};
var compilerOptions = program.getCompilerOptions();
var compilerOptions = host.getCompilerOptions();
var emitResolver = createResolver();
var checker: TypeChecker = {
getNodeCount: () => sum(program.getSourceFiles(), "nodeCount"),
getIdentifierCount: () => sum(program.getSourceFiles(), "identifierCount"),
getSymbolCount: () => sum(program.getSourceFiles(), "symbolCount"),
getNodeCount: () => sum(host.getSourceFiles(), "nodeCount"),
getIdentifierCount: () => sum(host.getSourceFiles(), "identifierCount"),
getSymbolCount: () => sum(host.getSourceFiles(), "symbolCount"),
getTypeCount: () => typeCount,
isUndefinedSymbol: symbol => symbol === undefinedSymbol,
isArgumentsSymbol: symbol => symbol === argumentsSymbol,
@ -55,8 +55,6 @@ module ts {
getSignatureFromDeclaration,
isImplementationOfOverload,
getAliasedSymbol: resolveImport,
hasEarlyErrors,
isEmitBlocked,
getEmitResolver: () => emitResolver,
};
@ -277,7 +275,7 @@ module ts {
return true;
}
var sourceFiles = program.getSourceFiles();
var sourceFiles = host.getSourceFiles();
return sourceFiles.indexOf(file1) <= sourceFiles.indexOf(file2);
}
@ -517,7 +515,7 @@ module ts {
}
while (true) {
var filename = normalizePath(combinePaths(searchPath, moduleName));
var sourceFile = program.getSourceFile(filename + ".ts") || program.getSourceFile(filename + ".d.ts");
var sourceFile = host.getSourceFile(filename + ".ts") || host.getSourceFile(filename + ".d.ts");
if (sourceFile || isRelative) break;
var parentPath = getDirectoryPath(searchPath);
if (parentPath === searchPath) break;
@ -3394,7 +3392,7 @@ module ts {
if (containingMessageChain) {
errorInfo = concatenateDiagnosticMessageChains(containingMessageChain, errorInfo);
}
addDiagnostic(createDiagnosticForNodeFromMessageChain(errorNode, errorInfo, program.getCompilerHost().getNewLine()));
addDiagnostic(createDiagnosticForNodeFromMessageChain(errorNode, errorInfo, host.getCompilerHost().getNewLine()));
}
return result !== Ternary.False;
@ -8323,7 +8321,7 @@ module ts {
var errorInfo = chainDiagnosticMessages(undefined, Diagnostics.Named_properties_0_of_types_1_and_2_are_not_identical, prop.name, typeName1, typeName2);
errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Interface_0_cannot_simultaneously_extend_types_1_and_2, typeToString(type), typeName1, typeName2);
addDiagnostic(createDiagnosticForNodeFromMessageChain(typeNode, errorInfo, program.getCompilerHost().getNewLine()));
addDiagnostic(createDiagnosticForNodeFromMessageChain(typeNode, errorInfo, host.getCompilerHost().getNewLine()));
}
}
}
@ -8934,7 +8932,7 @@ module ts {
checkSourceFile(sourceFile);
return filter(getSortedDiagnostics(), d => d.file === sourceFile);
}
forEach(program.getSourceFiles(), checkSourceFile);
forEach(host.getSourceFiles(), checkSourceFile);
return getSortedDiagnostics();
}
@ -9451,16 +9449,6 @@ module ts {
return getDiagnostics(sourceFile).length > 0 || getGlobalDiagnostics().length > 0;
}
function isEmitBlocked(sourceFile?: SourceFile): boolean {
return program.getDiagnostics(sourceFile).length !== 0 ||
hasEarlyErrors(sourceFile) ||
(compilerOptions.noEmitOnError && getDiagnostics(sourceFile).length !== 0);
}
function hasEarlyErrors(sourceFile?: SourceFile): boolean {
return forEach(getDiagnostics(sourceFile), d => d.isEarly);
}
function isImportResolvedToValue(symbol: Symbol): boolean {
var target = resolveImport(symbol);
// const enums and modules that contain only const enums are not considered values from the emit perespective
@ -9556,7 +9544,6 @@ module ts {
getEnumMemberValue,
isTopLevelValueImportWithEntityName,
hasSemanticErrors,
isEmitBlocked,
isDeclarationVisible,
isImplementationOfOverload,
writeTypeOfDeclaration,
@ -9570,12 +9557,12 @@ module ts {
function initializeTypeChecker() {
// Bind all source files and propagate errors
forEach(program.getSourceFiles(), file => {
forEach(host.getSourceFiles(), file => {
bindSourceFile(file);
forEach(file.semanticDiagnostics, addDiagnostic);
});
// Initialize global symbol table
forEach(program.getSourceFiles(), file => {
forEach(host.getSourceFiles(), file => {
if (!isExternalModule(file)) {
extendSymbolTable(globals, file.locals);
}

View File

@ -4138,7 +4138,7 @@ module ts {
if (targetSourceFile === undefined) {
// No targetSourceFile is specified (e.g. calling emitter from batch compiler)
hasSemanticErrors = resolver.hasSemanticErrors();
isEmitBlocked = resolver.isEmitBlocked();
isEmitBlocked = host.isEmitBlocked();
forEach(host.getSourceFiles(), sourceFile => {
if (shouldEmitToOwnFile(sourceFile, compilerOptions)) {
@ -4156,7 +4156,7 @@ module ts {
if (shouldEmitToOwnFile(targetSourceFile, compilerOptions)) {
// If shouldEmitToOwnFile returns true or targetSourceFile is an external module file, then emit targetSourceFile in its own output file
hasSemanticErrors = resolver.hasSemanticErrors(targetSourceFile);
isEmitBlocked = resolver.isEmitBlocked(targetSourceFile);
isEmitBlocked = host.isEmitBlocked(targetSourceFile);
var jsFilePath = getOwnEmitOutputFilePath(targetSourceFile, host, ".js");
emitFile(jsFilePath, targetSourceFile);
@ -4167,7 +4167,7 @@ module ts {
forEach(host.getSourceFiles(), sourceFile => {
if (!shouldEmitToOwnFile(sourceFile, compilerOptions)) {
hasSemanticErrors = hasSemanticErrors || resolver.hasSemanticErrors(sourceFile);
isEmitBlocked = isEmitBlocked || resolver.isEmitBlocked(sourceFile);
isEmitBlocked = isEmitBlocked || host.isEmitBlocked(sourceFile);
}
});

View File

@ -102,13 +102,28 @@ module ts {
getDeclarationDiagnostics: getDeclarationDiagnostics,
getTypeChecker,
getCommonSourceDirectory: () => commonSourceDirectory,
emitFiles: invokeEmitter
emitFiles: invokeEmitter,
isEmitBlocked,
};
return program;
function hasEarlyErrors(sourceFile?: SourceFile): boolean {
return forEach(getDiagnosticsProducingTypeChecker().getDiagnostics(sourceFile), d => d.isEarly);
}
function isEmitBlocked(sourceFile?: SourceFile): boolean {
return getDiagnostics(sourceFile).length !== 0 ||
hasEarlyErrors(sourceFile) ||
(options.noEmitOnError && getDiagnosticsProducingTypeChecker().getDiagnostics(sourceFile).length !== 0);
}
function getDiagnosticsProducingTypeChecker() {
return diagnosticsProducingTypeChecker || (diagnosticsProducingTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ true));
}
function getTypeChecker(produceDiagnostics: boolean) {
if (produceDiagnostics) {
return diagnosticsProducingTypeChecker || (diagnosticsProducingTypeChecker = createTypeChecker(program, produceDiagnostics));
return getDiagnosticsProducingTypeChecker();
}
else {
return noDiagnosticsTypeChecker || (noDiagnosticsTypeChecker = createTypeChecker(program, produceDiagnostics));
@ -116,14 +131,14 @@ module ts {
}
function getDeclarationDiagnostics(targetSourceFile: SourceFile): Diagnostic[]{
var fullTypeChecker = getTypeChecker(/*produceDiagnostics:*/true);
fullTypeChecker.getDiagnostics(targetSourceFile);
var resolver = fullTypeChecker.getEmitResolver();
var typeChecker = getDiagnosticsProducingTypeChecker();
typeChecker.getDiagnostics(targetSourceFile);
var resolver = typeChecker.getEmitResolver();
return ts.getDeclarationDiagnostics(program, resolver, targetSourceFile);
}
function invokeEmitter(targetSourceFile?: SourceFile) {
var resolver = getTypeChecker(/*produceDiagnostics:*/true).getEmitResolver();
var resolver = getDiagnosticsProducingTypeChecker().getEmitResolver();
return emitFiles(resolver, program, targetSourceFile);
}

View File

@ -286,7 +286,7 @@ module ts {
var checker = program.getTypeChecker(/*fullTypeCheckMode*/ true);
var checkStart = new Date().getTime();
errors = checker.getDiagnostics();
if (checker.isEmitBlocked()) {
if (program.isEmitBlocked()) {
exitStatus = EmitReturnStatus.AllOutputGenerationSkipped;
}
else {

View File

@ -926,7 +926,9 @@ module ts {
// will throw an invalid operation exception.
getTypeChecker(produceDiagnostics: boolean): TypeChecker;
getCommonSourceDirectory(): string;
emitFiles(targetSourceFile?: SourceFile): EmitResult;
isEmitBlocked(sourceFile?: SourceFile): boolean;
}
export interface SourceMapSpan {
@ -966,6 +968,32 @@ module ts {
sourceMaps: SourceMapData[]; // Array of sourceMapData if compiler emitted sourcemaps
}
export interface TypeCheckerHost {
getCompilerOptions(): CompilerOptions;
getCompilerHost(): CompilerHost;
getSourceFiles(): SourceFile[];
getSourceFile(filename: string): SourceFile;
//getSourceFiles(): SourceFile[];
//getCompilerOptions(): CompilerOptions;
//getDiagnostics(sourceFile?: SourceFile): Diagnostic[];
//getGlobalDiagnostics(): Diagnostic[];
//getDeclarationDiagnostics(sourceFile: SourceFile): Diagnostic[];
//// Gets a type checker that can be used to semantically analyze source fils in the program.
//// The 'produceDiagnostics' flag determines if the checker will produce diagnostics while
//// analyzing the code. It can be set to 'false' to make many type checking operaitons
//// faster. With this flag set, the checker can avoid codepaths only necessary to produce
//// diagnostics, but not necessary to answer semantic questions about the code.
////
//// If 'produceDiagnostics' is false, then any calls to get diagnostics from the TypeChecker
//// will throw an invalid operation exception.
//getTypeChecker(produceDiagnostics: boolean): TypeChecker;
//getCommonSourceDirectory(): string;
//emitFiles(targetSourceFile?: SourceFile): EmitResult;
}
export interface TypeChecker {
getEmitResolver(): EmitResolver;
getDiagnostics(sourceFile?: SourceFile): Diagnostic[];
@ -997,7 +1025,7 @@ module ts {
isImplementationOfOverload(node: FunctionLikeDeclaration): boolean;
isUndefinedSymbol(symbol: Symbol): boolean;
isArgumentsSymbol(symbol: Symbol): boolean;
isEmitBlocked(sourceFile?: SourceFile): boolean;
// Returns the constant value of this enum member, or 'undefined' if the enum member has a computed value.
getEnumMemberValue(node: EnumMember): number;
isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean;
@ -1080,6 +1108,7 @@ module ts {
getCompilerHost(): CompilerHost;
getCompilerOptions(): CompilerOptions;
getCommonSourceDirectory(): string;
isEmitBlocked(sourceFile?: SourceFile): boolean;
}
export interface EmitResolver {
@ -1099,7 +1128,6 @@ module ts {
isEntityNameVisible(entityName: EntityName, enclosingDeclaration: Node): SymbolVisibilityResult;
// Returns the constant value this property access resolves to, or 'undefined' for a non-constant
getConstantValue(node: PropertyAccessExpression | ElementAccessExpression): number;
isEmitBlocked(sourceFile?: SourceFile): boolean;
isUnknownIdentifier(location: Node, name: string): boolean;
}

View File

@ -1046,7 +1046,7 @@ module Harness {
var checker = program.getTypeChecker(/*produceDiagnostics*/ true);
var isEmitBlocked = checker.isEmitBlocked();
var isEmitBlocked = program.isEmitBlocked();
// only emit if there weren't parse errors
var emitResult: ts.EmitResult;