From 4aa361d4bf9f6952a9d36b3840b1e445519386be Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Dec 2014 13:14:14 -0800 Subject: [PATCH 1/8] Layer the compiler so that every layer only depends on hte layers below it. The layering is now: types core scanner parser binder checker emitter program --- src/compiler/binder.ts | 4 - src/compiler/checker.ts | 22 +- src/compiler/emitter.ts | 6 +- src/compiler/parser.ts | 346 ------------------------------- src/compiler/program.ts | 374 ++++++++++++++++++++++++++++++++++ src/compiler/scanner.ts | 1 - src/compiler/tsc.ts | 11 +- src/compiler/types.ts | 9 +- src/harness/fourslash.ts | 2 +- src/harness/harness.ts | 4 +- src/harness/projectsRunner.ts | 2 +- src/services/services.ts | 11 +- 12 files changed, 390 insertions(+), 402 deletions(-) create mode 100644 src/compiler/program.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 4f8141fd86..d62a36b1bc 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1,10 +1,6 @@ -/// -/// -/// /// module ts { - export const enum ModuleInstanceState { NonInstantiated = 0, Instantiated = 1, diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 20033d4c80..8f133d73cd 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1,10 +1,4 @@ -/// -/// -/// -/// /// -/// -/// module ts { var nextSymbolId = 1; @@ -16,7 +10,6 @@ module ts { /// 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, fullTypeCheck: boolean): TypeChecker { - var Symbol = objectAllocator.getSymbolConstructor(); var Type = objectAllocator.getTypeConstructor(); var Signature = objectAllocator.getSignatureConstructor(); @@ -27,6 +20,7 @@ module ts { var emptySymbols: SymbolTable = {}; var compilerOptions = program.getCompilerOptions(); + var emitResolver = createResolver(); var checker: TypeChecker = { getProgram: () => program, @@ -36,9 +30,7 @@ module ts { getTypeCount: () => typeCount, isUndefinedSymbol: symbol => symbol === undefinedSymbol, isArgumentsSymbol: symbol => symbol === argumentsSymbol, - emitFiles: invokeEmitter, getDiagnostics, - getDeclarationDiagnostics, getGlobalDiagnostics, getTypeOfSymbolAtLocation, getDeclaredTypeOfSymbol, @@ -66,6 +58,7 @@ module ts { getAliasedSymbol: resolveImport, hasEarlyErrors, isEmitBlocked, + getEmitResolver: () => emitResolver, }; var undefinedSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "undefined"); @@ -8945,12 +8938,6 @@ module ts { return getSortedDiagnostics(); } - function getDeclarationDiagnostics(targetSourceFile: SourceFile): Diagnostic[] { - var resolver = createResolver(); - checkSourceFile(targetSourceFile); - return ts.getDeclarationDiagnostics(program, resolver, targetSourceFile); - } - function getGlobalDiagnostics(): Diagnostic[] { return filter(getSortedDiagnostics(), d => !d.file); } @@ -9575,11 +9562,6 @@ module ts { }; } - function invokeEmitter(targetSourceFile?: SourceFile) { - var resolver = createResolver(); - return emitFiles(resolver, targetSourceFile); - } - function initializeTypeChecker() { // Bind all source files and propagate errors forEach(program.getSourceFiles(), file => { diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 0f3e960681..1dd0e4cd09 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1,8 +1,4 @@ -/// -/// -/// -/// -/// +/// module ts { interface EmitTextWriter { diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 582924df04..c5337a6581 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1,5 +1,3 @@ -/// -/// /// /// @@ -260,79 +258,6 @@ module ts { } } - // TODO (drosen, mhegazy): Move to a more appropriate file. - export function createCompilerHost(options: CompilerOptions): CompilerHost { - var currentDirectory: string; - var existingDirectories: Map = {}; - - function getCanonicalFileName(fileName: string): string { - // if underlying system can distinguish between two files whose names differs only in cases then file name already in canonical form. - // otherwise use toLowerCase as a canonical form. - return sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(); - } - - // returned by CScript sys environment - var unsupportedFileEncodingErrorCode = -2147024809; - - function getSourceFile(filename: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile { - try { - var text = sys.readFile(filename, options.charset); - } - catch (e) { - if (onError) { - onError(e.number === unsupportedFileEncodingErrorCode ? - createCompilerDiagnostic(Diagnostics.Unsupported_file_encoding).messageText : - e.message); - } - text = ""; - } - - return text !== undefined ? createSourceFile(filename, text, languageVersion) : undefined; - } - - function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) { - function directoryExists(directoryPath: string): boolean { - if (hasProperty(existingDirectories, directoryPath)) { - return true; - } - if (sys.directoryExists(directoryPath)) { - existingDirectories[directoryPath] = true; - return true; - } - return false; - } - - function ensureDirectoriesExist(directoryPath: string) { - if (directoryPath.length > getRootLength(directoryPath) && !directoryExists(directoryPath)) { - var parentDirectory = getDirectoryPath(directoryPath); - ensureDirectoriesExist(parentDirectory); - sys.createDirectory(directoryPath); - } - } - - try { - ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); - sys.writeFile(fileName, data, writeByteOrderMark); - } - catch (e) { - if (onError) { - onError(e.message); - } - } - } - - return { - getSourceFile, - getDefaultLibFilename: options => combinePaths(getDirectoryPath(normalizePath(sys.getExecutingFilePath())), options.target === ScriptTarget.ES6 ? "lib.es6.d.ts" : "lib.d.ts"), - writeFile, - getCurrentDirectory: () => currentDirectory || (currentDirectory = sys.getCurrentDirectory()), - useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames, - getCanonicalFileName, - getNewLine: () => sys.newLine - }; - } - - const enum ParsingContext { SourceElements, // Elements in source file ModuleElements, // Elements in module declaration @@ -5265,275 +5190,4 @@ module ts { return grammarErrorOnFirstToken(node, Diagnostics.yield_expressions_are_not_currently_supported); } } - - export function createProgram(rootNames: string[], options: CompilerOptions, host: CompilerHost): Program { - var program: Program; - var files: SourceFile[] = []; - var filesByName: Map = {}; - var errors: Diagnostic[] = []; - var seenNoDefaultLib = options.noLib; - var commonSourceDirectory: string; - - forEach(rootNames, name => processRootFile(name, false)); - if (!seenNoDefaultLib) { - processRootFile(host.getDefaultLibFilename(options), true); - } - verifyCompilerOptions(); - errors.sort(compareDiagnostics); - program = { - getSourceFile: getSourceFile, - getSourceFiles: () => files, - getCompilerOptions: () => options, - getCompilerHost: () => host, - getDiagnostics: getDiagnostics, - getGlobalDiagnostics: getGlobalDiagnostics, - getTypeChecker: fullTypeCheckMode => createTypeChecker(program, fullTypeCheckMode), - getCommonSourceDirectory: () => commonSourceDirectory, - }; - return program; - - function getSourceFile(filename: string) { - filename = host.getCanonicalFileName(filename); - return hasProperty(filesByName, filename) ? filesByName[filename] : undefined; - } - - function getDiagnostics(sourceFile?: SourceFile): Diagnostic[] { - return sourceFile ? filter(errors, e => e.file === sourceFile) : errors; - } - - function getGlobalDiagnostics(): Diagnostic[] { - return filter(errors, e => !e.file); - } - - function hasExtension(filename: string): boolean { - return getBaseFilename(filename).indexOf(".") >= 0; - } - - function processRootFile(filename: string, isDefaultLib: boolean) { - processSourceFile(normalizePath(filename), isDefaultLib); - } - - function processSourceFile(filename: string, isDefaultLib: boolean, refFile?: SourceFile, refPos?: number, refEnd?: number) { - if (refEnd !== undefined && refPos !== undefined) { - var start = refPos; - var length = refEnd - refPos; - } - var diagnostic: DiagnosticMessage; - if (hasExtension(filename)) { - if (!options.allowNonTsExtensions && !fileExtensionIs(filename, ".ts")) { - diagnostic = Diagnostics.File_0_must_have_extension_ts_or_d_ts; - } - else if (!findSourceFile(filename, isDefaultLib, refFile, refPos, refEnd)) { - diagnostic = Diagnostics.File_0_not_found; - } - else if (refFile && host.getCanonicalFileName(filename) === host.getCanonicalFileName(refFile.filename)) { - diagnostic = Diagnostics.A_file_cannot_have_a_reference_to_itself; - } - } - else { - if (!(findSourceFile(filename + ".ts", isDefaultLib, refFile, refPos, refEnd) || findSourceFile(filename + ".d.ts", isDefaultLib, refFile, refPos, refEnd))) { - diagnostic = Diagnostics.File_0_not_found; - filename += ".ts"; - } - } - - if (diagnostic) { - if (refFile) { - errors.push(createFileDiagnostic(refFile, start, length, diagnostic, filename)); - } - else { - errors.push(createCompilerDiagnostic(diagnostic, filename)); - } - } - } - - // Get source file from normalized filename - function findSourceFile(filename: string, isDefaultLib: boolean, refFile?: SourceFile, refStart?: number, refLength?: number): SourceFile { - var canonicalName = host.getCanonicalFileName(filename); - if (hasProperty(filesByName, canonicalName)) { - // We've already looked for this file, use cached result - return getSourceFileFromCache(filename, canonicalName, /*useAbsolutePath*/ false); - } - else { - var normalizedAbsolutePath = getNormalizedAbsolutePath(filename, host.getCurrentDirectory()); - var canonicalAbsolutePath = host.getCanonicalFileName(normalizedAbsolutePath); - if (hasProperty(filesByName, canonicalAbsolutePath)) { - return getSourceFileFromCache(normalizedAbsolutePath, canonicalAbsolutePath, /*useAbsolutePath*/ true); - } - - // We haven't looked for this file, do so now and cache result - var file = filesByName[canonicalName] = host.getSourceFile(filename, options.target, hostErrorMessage => { - errors.push(createFileDiagnostic(refFile, refStart, refLength, - Diagnostics.Cannot_read_file_0_Colon_1, filename, hostErrorMessage)); - }); - if (file) { - seenNoDefaultLib = seenNoDefaultLib || file.hasNoDefaultLib; - - // Set the source file for normalized absolute path - filesByName[canonicalAbsolutePath] = file; - - if (!options.noResolve) { - var basePath = getDirectoryPath(filename); - processReferencedFiles(file, basePath); - processImportedModules(file, basePath); - } - if (isDefaultLib) { - files.unshift(file); - } - else { - files.push(file); - } - forEach(file.getSyntacticDiagnostics(), e => { - errors.push(e); - }); - } - } - return file; - - function getSourceFileFromCache(filename: string, canonicalName: string, useAbsolutePath: boolean): SourceFile { - var file = filesByName[canonicalName]; - if (file && host.useCaseSensitiveFileNames()) { - var sourceFileName = useAbsolutePath ? getNormalizedAbsolutePath(file.filename, host.getCurrentDirectory()) : file.filename; - if (canonicalName !== sourceFileName) { - errors.push(createFileDiagnostic(refFile, refStart, refLength, - Diagnostics.Filename_0_differs_from_already_included_filename_1_only_in_casing, filename, sourceFileName)); - } - } - return file; - } - } - - function processReferencedFiles(file: SourceFile, basePath: string) { - forEach(file.referencedFiles, ref => { - var referencedFilename = isRootedDiskPath(ref.filename) ? ref.filename : combinePaths(basePath, ref.filename); - processSourceFile(normalizePath(referencedFilename), /* isDefaultLib */ false, file, ref.pos, ref.end); - }); - } - - function processImportedModules(file: SourceFile, basePath: string) { - forEach(file.statements, node => { - if (isExternalModuleImportDeclaration(node) && - getExternalModuleImportDeclarationExpression(node).kind === SyntaxKind.StringLiteral) { - - var nameLiteral = getExternalModuleImportDeclarationExpression(node); - var moduleName = nameLiteral.text; - if (moduleName) { - var searchPath = basePath; - while (true) { - var searchName = normalizePath(combinePaths(searchPath, moduleName)); - if (findModuleSourceFile(searchName + ".ts", nameLiteral) || findModuleSourceFile(searchName + ".d.ts", nameLiteral)) { - break; - } - - var parentPath = getDirectoryPath(searchPath); - if (parentPath === searchPath) { - break; - } - searchPath = parentPath; - } - } - } - else if (node.kind === SyntaxKind.ModuleDeclaration && (node).name.kind === SyntaxKind.StringLiteral && (node.flags & NodeFlags.Ambient || isDeclarationFile(file))) { - // TypeScript 1.0 spec (April 2014): 12.1.6 - // An AmbientExternalModuleDeclaration declares an external module. - // This type of declaration is permitted only in the global module. - // The StringLiteral must specify a top - level external module name. - // Relative external module names are not permitted - forEachChild((node).body, node => { - if (isExternalModuleImportDeclaration(node) && - getExternalModuleImportDeclarationExpression(node).kind === SyntaxKind.StringLiteral) { - - var nameLiteral = getExternalModuleImportDeclarationExpression(node); - var moduleName = nameLiteral.text; - if (moduleName) { - // TypeScript 1.0 spec (April 2014): 12.1.6 - // An ExternalImportDeclaration in anAmbientExternalModuleDeclaration may reference other external modules - // only through top - level external module names. Relative external module names are not permitted. - var searchName = normalizePath(combinePaths(basePath, moduleName)); - var tsFile = findModuleSourceFile(searchName + ".ts", nameLiteral); - if (!tsFile) { - findModuleSourceFile(searchName + ".d.ts", nameLiteral); - } - } - } - }); - } - }); - - function findModuleSourceFile(filename: string, nameLiteral: LiteralExpression) { - return findSourceFile(filename, /* isDefaultLib */ false, file, nameLiteral.pos, nameLiteral.end - nameLiteral.pos); - } - } - - function verifyCompilerOptions() { - if (!options.sourceMap && (options.mapRoot || options.sourceRoot)) { - // Error to specify --mapRoot or --sourceRoot without mapSourceFiles - if (options.mapRoot) { - errors.push(createCompilerDiagnostic(Diagnostics.Option_mapRoot_cannot_be_specified_without_specifying_sourcemap_option)); - } - if (options.sourceRoot) { - errors.push(createCompilerDiagnostic(Diagnostics.Option_sourceRoot_cannot_be_specified_without_specifying_sourcemap_option)); - } - return; - } - - var firstExternalModule = forEach(files, f => isExternalModule(f) ? f : undefined); - if (firstExternalModule && options.module === ModuleKind.None) { - // We cannot use createDiagnosticFromNode because nodes do not have parents yet - var externalModuleErrorSpan = getErrorSpanForNode(firstExternalModule.externalModuleIndicator); - var errorStart = skipTrivia(firstExternalModule.text, externalModuleErrorSpan.pos); - var errorLength = externalModuleErrorSpan.end - errorStart; - errors.push(createFileDiagnostic(firstExternalModule, errorStart, errorLength, Diagnostics.Cannot_compile_external_modules_unless_the_module_flag_is_provided)); - } - - // there has to be common source directory if user specified --outdir || --sourcRoot - // if user specified --mapRoot, there needs to be common source directory if there would be multiple files being emitted - if (options.outDir || // there is --outDir specified - options.sourceRoot || // there is --sourceRoot specified - (options.mapRoot && // there is --mapRoot Specified and there would be multiple js files generated - (!options.out || firstExternalModule !== undefined))) { - - var commonPathComponents: string[]; - forEach(files, sourceFile => { - // Each file contributes into common source file path - if (!(sourceFile.flags & NodeFlags.DeclarationFile) - && !fileExtensionIs(sourceFile.filename, ".js")) { - var sourcePathComponents = getNormalizedPathComponents(sourceFile.filename, host.getCurrentDirectory()); - sourcePathComponents.pop(); // FileName is not part of directory - if (commonPathComponents) { - for (var i = 0; i < Math.min(commonPathComponents.length, sourcePathComponents.length); i++) { - if (commonPathComponents[i] !== sourcePathComponents[i]) { - if (i === 0) { - errors.push(createCompilerDiagnostic(Diagnostics.Cannot_find_the_common_subdirectory_path_for_the_input_files)); - return; - } - - // New common path found that is 0 -> i-1 - commonPathComponents.length = i; - break; - } - } - - // If the fileComponent path completely matched and less than already found update the length - if (sourcePathComponents.length < commonPathComponents.length) { - commonPathComponents.length = sourcePathComponents.length; - } - } - else { - // first file - commonPathComponents = sourcePathComponents; - } - } - }); - - commonSourceDirectory = getNormalizedPathFromPathComponents(commonPathComponents); - if (commonSourceDirectory) { - // Make sure directory path ends with directory separator so this string can directly - // used to replace with "" to get the relative path of the source file and the relative path doesn't - // start with / making it rooted path - commonSourceDirectory += directorySeparator; - } - } - } - } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts new file mode 100644 index 0000000000..7fb77dc9bd --- /dev/null +++ b/src/compiler/program.ts @@ -0,0 +1,374 @@ +/// +/// + +module ts { + // TODO (drosen, mhegazy): Move to a more appropriate file. + export function createCompilerHost(options: CompilerOptions): CompilerHost { + var currentDirectory: string; + var existingDirectories: Map = {}; + + function getCanonicalFileName(fileName: string): string { + // if underlying system can distinguish between two files whose names differs only in cases then file name already in canonical form. + // otherwise use toLowerCase as a canonical form. + return sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(); + } + + // returned by CScript sys environment + var unsupportedFileEncodingErrorCode = -2147024809; + + function getSourceFile(filename: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile { + try { + var text = sys.readFile(filename, options.charset); + } + catch (e) { + if (onError) { + onError(e.number === unsupportedFileEncodingErrorCode ? + createCompilerDiagnostic(Diagnostics.Unsupported_file_encoding).messageText : + e.message); + } + text = ""; + } + + return text !== undefined ? createSourceFile(filename, text, languageVersion) : undefined; + } + + function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) { + function directoryExists(directoryPath: string): boolean { + if (hasProperty(existingDirectories, directoryPath)) { + return true; + } + if (sys.directoryExists(directoryPath)) { + existingDirectories[directoryPath] = true; + return true; + } + return false; + } + + function ensureDirectoriesExist(directoryPath: string) { + if (directoryPath.length > getRootLength(directoryPath) && !directoryExists(directoryPath)) { + var parentDirectory = getDirectoryPath(directoryPath); + ensureDirectoriesExist(parentDirectory); + sys.createDirectory(directoryPath); + } + } + + try { + ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); + sys.writeFile(fileName, data, writeByteOrderMark); + } + catch (e) { + if (onError) { + onError(e.message); + } + } + } + + return { + getSourceFile, + getDefaultLibFilename: options => combinePaths(getDirectoryPath(normalizePath(sys.getExecutingFilePath())), options.target === ScriptTarget.ES6 ? "lib.es6.d.ts" : "lib.d.ts"), + writeFile, + getCurrentDirectory: () => currentDirectory || (currentDirectory = sys.getCurrentDirectory()), + useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames, + getCanonicalFileName, + getNewLine: () => sys.newLine + }; + } + + export function createProgram(rootNames: string[], options: CompilerOptions, host: CompilerHost): Program { + var program: Program; + var files: SourceFile[] = []; + var filesByName: Map = {}; + var errors: Diagnostic[] = []; + var seenNoDefaultLib = options.noLib; + var commonSourceDirectory: string; + + forEach(rootNames, name => processRootFile(name, false)); + if (!seenNoDefaultLib) { + processRootFile(host.getDefaultLibFilename(options), true); + } + verifyCompilerOptions(); + errors.sort(compareDiagnostics); + + var fullTypeChecker: TypeChecker; + var nonFullTypeChecker: TypeChecker; + + function getTypeChecker(full: boolean) { + if (full) { + return fullTypeChecker || (fullTypeChecker = createTypeChecker(program, full)); + } + else { + return nonFullTypeChecker || (nonFullTypeChecker = createTypeChecker(program, full)); + } + } + + program = { + getSourceFile: getSourceFile, + getSourceFiles: () => files, + getCompilerOptions: () => options, + getCompilerHost: () => host, + getDiagnostics: getDiagnostics, + getGlobalDiagnostics: getGlobalDiagnostics, + getDeclarationDiagnostics: getDeclarationDiagnostics, + getTypeChecker, + getCommonSourceDirectory: () => commonSourceDirectory, + emitFiles: invokeEmitter + }; + return program; + + function getDeclarationDiagnostics(targetSourceFile: SourceFile): Diagnostic[]{ + var fullTypeChecker = getTypeChecker(/*full:*/ true); + fullTypeChecker.getDiagnostics(targetSourceFile); + var resolver = fullTypeChecker.getEmitResolver(); + return ts.getDeclarationDiagnostics(program, resolver, targetSourceFile); + } + + function invokeEmitter(targetSourceFile?: SourceFile) { + var resolver = getTypeChecker(/*full:*/ true).getEmitResolver(); + return emitFiles(resolver, targetSourceFile); + } + + function getSourceFile(filename: string) { + filename = host.getCanonicalFileName(filename); + return hasProperty(filesByName, filename) ? filesByName[filename] : undefined; + } + + function getDiagnostics(sourceFile?: SourceFile): Diagnostic[] { + return sourceFile ? filter(errors, e => e.file === sourceFile) : errors; + } + + function getGlobalDiagnostics(): Diagnostic[] { + return filter(errors, e => !e.file); + } + + function hasExtension(filename: string): boolean { + return getBaseFilename(filename).indexOf(".") >= 0; + } + + function processRootFile(filename: string, isDefaultLib: boolean) { + processSourceFile(normalizePath(filename), isDefaultLib); + } + + function processSourceFile(filename: string, isDefaultLib: boolean, refFile?: SourceFile, refPos?: number, refEnd?: number) { + if (refEnd !== undefined && refPos !== undefined) { + var start = refPos; + var length = refEnd - refPos; + } + var diagnostic: DiagnosticMessage; + if (hasExtension(filename)) { + if (!options.allowNonTsExtensions && !fileExtensionIs(filename, ".ts")) { + diagnostic = Diagnostics.File_0_must_have_extension_ts_or_d_ts; + } + else if (!findSourceFile(filename, isDefaultLib, refFile, refPos, refEnd)) { + diagnostic = Diagnostics.File_0_not_found; + } + else if (refFile && host.getCanonicalFileName(filename) === host.getCanonicalFileName(refFile.filename)) { + diagnostic = Diagnostics.A_file_cannot_have_a_reference_to_itself; + } + } + else { + if (!(findSourceFile(filename + ".ts", isDefaultLib, refFile, refPos, refEnd) || findSourceFile(filename + ".d.ts", isDefaultLib, refFile, refPos, refEnd))) { + diagnostic = Diagnostics.File_0_not_found; + filename += ".ts"; + } + } + + if (diagnostic) { + if (refFile) { + errors.push(createFileDiagnostic(refFile, start, length, diagnostic, filename)); + } + else { + errors.push(createCompilerDiagnostic(diagnostic, filename)); + } + } + } + + // Get source file from normalized filename + function findSourceFile(filename: string, isDefaultLib: boolean, refFile?: SourceFile, refStart?: number, refLength?: number): SourceFile { + var canonicalName = host.getCanonicalFileName(filename); + if (hasProperty(filesByName, canonicalName)) { + // We've already looked for this file, use cached result + return getSourceFileFromCache(filename, canonicalName, /*useAbsolutePath*/ false); + } + else { + var normalizedAbsolutePath = getNormalizedAbsolutePath(filename, host.getCurrentDirectory()); + var canonicalAbsolutePath = host.getCanonicalFileName(normalizedAbsolutePath); + if (hasProperty(filesByName, canonicalAbsolutePath)) { + return getSourceFileFromCache(normalizedAbsolutePath, canonicalAbsolutePath, /*useAbsolutePath*/ true); + } + + // We haven't looked for this file, do so now and cache result + var file = filesByName[canonicalName] = host.getSourceFile(filename, options.target, hostErrorMessage => { + errors.push(createFileDiagnostic(refFile, refStart, refLength, + Diagnostics.Cannot_read_file_0_Colon_1, filename, hostErrorMessage)); + }); + if (file) { + seenNoDefaultLib = seenNoDefaultLib || file.hasNoDefaultLib; + + // Set the source file for normalized absolute path + filesByName[canonicalAbsolutePath] = file; + + if (!options.noResolve) { + var basePath = getDirectoryPath(filename); + processReferencedFiles(file, basePath); + processImportedModules(file, basePath); + } + if (isDefaultLib) { + files.unshift(file); + } + else { + files.push(file); + } + forEach(file.getSyntacticDiagnostics(), e => { + errors.push(e); + }); + } + } + return file; + + function getSourceFileFromCache(filename: string, canonicalName: string, useAbsolutePath: boolean): SourceFile { + var file = filesByName[canonicalName]; + if (file && host.useCaseSensitiveFileNames()) { + var sourceFileName = useAbsolutePath ? getNormalizedAbsolutePath(file.filename, host.getCurrentDirectory()) : file.filename; + if (canonicalName !== sourceFileName) { + errors.push(createFileDiagnostic(refFile, refStart, refLength, + Diagnostics.Filename_0_differs_from_already_included_filename_1_only_in_casing, filename, sourceFileName)); + } + } + return file; + } + } + + function processReferencedFiles(file: SourceFile, basePath: string) { + forEach(file.referencedFiles, ref => { + var referencedFilename = isRootedDiskPath(ref.filename) ? ref.filename : combinePaths(basePath, ref.filename); + processSourceFile(normalizePath(referencedFilename), /* isDefaultLib */ false, file, ref.pos, ref.end); + }); + } + + function processImportedModules(file: SourceFile, basePath: string) { + forEach(file.statements, node => { + if (isExternalModuleImportDeclaration(node) && + getExternalModuleImportDeclarationExpression(node).kind === SyntaxKind.StringLiteral) { + + var nameLiteral = getExternalModuleImportDeclarationExpression(node); + var moduleName = nameLiteral.text; + if (moduleName) { + var searchPath = basePath; + while (true) { + var searchName = normalizePath(combinePaths(searchPath, moduleName)); + if (findModuleSourceFile(searchName + ".ts", nameLiteral) || findModuleSourceFile(searchName + ".d.ts", nameLiteral)) { + break; + } + + var parentPath = getDirectoryPath(searchPath); + if (parentPath === searchPath) { + break; + } + searchPath = parentPath; + } + } + } + else if (node.kind === SyntaxKind.ModuleDeclaration && (node).name.kind === SyntaxKind.StringLiteral && (node.flags & NodeFlags.Ambient || isDeclarationFile(file))) { + // TypeScript 1.0 spec (April 2014): 12.1.6 + // An AmbientExternalModuleDeclaration declares an external module. + // This type of declaration is permitted only in the global module. + // The StringLiteral must specify a top - level external module name. + // Relative external module names are not permitted + forEachChild((node).body, node => { + if (isExternalModuleImportDeclaration(node) && + getExternalModuleImportDeclarationExpression(node).kind === SyntaxKind.StringLiteral) { + + var nameLiteral = getExternalModuleImportDeclarationExpression(node); + var moduleName = nameLiteral.text; + if (moduleName) { + // TypeScript 1.0 spec (April 2014): 12.1.6 + // An ExternalImportDeclaration in anAmbientExternalModuleDeclaration may reference other external modules + // only through top - level external module names. Relative external module names are not permitted. + var searchName = normalizePath(combinePaths(basePath, moduleName)); + var tsFile = findModuleSourceFile(searchName + ".ts", nameLiteral); + if (!tsFile) { + findModuleSourceFile(searchName + ".d.ts", nameLiteral); + } + } + } + }); + } + }); + + function findModuleSourceFile(filename: string, nameLiteral: LiteralExpression) { + return findSourceFile(filename, /* isDefaultLib */ false, file, nameLiteral.pos, nameLiteral.end - nameLiteral.pos); + } + } + + function verifyCompilerOptions() { + if (!options.sourceMap && (options.mapRoot || options.sourceRoot)) { + // Error to specify --mapRoot or --sourceRoot without mapSourceFiles + if (options.mapRoot) { + errors.push(createCompilerDiagnostic(Diagnostics.Option_mapRoot_cannot_be_specified_without_specifying_sourcemap_option)); + } + if (options.sourceRoot) { + errors.push(createCompilerDiagnostic(Diagnostics.Option_sourceRoot_cannot_be_specified_without_specifying_sourcemap_option)); + } + return; + } + + var firstExternalModule = forEach(files, f => isExternalModule(f) ? f : undefined); + if (firstExternalModule && options.module === ModuleKind.None) { + // We cannot use createDiagnosticFromNode because nodes do not have parents yet + var externalModuleErrorSpan = getErrorSpanForNode(firstExternalModule.externalModuleIndicator); + var errorStart = skipTrivia(firstExternalModule.text, externalModuleErrorSpan.pos); + var errorLength = externalModuleErrorSpan.end - errorStart; + errors.push(createFileDiagnostic(firstExternalModule, errorStart, errorLength, Diagnostics.Cannot_compile_external_modules_unless_the_module_flag_is_provided)); + } + + // there has to be common source directory if user specified --outdir || --sourcRoot + // if user specified --mapRoot, there needs to be common source directory if there would be multiple files being emitted + if (options.outDir || // there is --outDir specified + options.sourceRoot || // there is --sourceRoot specified + (options.mapRoot && // there is --mapRoot Specified and there would be multiple js files generated + (!options.out || firstExternalModule !== undefined))) { + + var commonPathComponents: string[]; + forEach(files, sourceFile => { + // Each file contributes into common source file path + if (!(sourceFile.flags & NodeFlags.DeclarationFile) + && !fileExtensionIs(sourceFile.filename, ".js")) { + var sourcePathComponents = getNormalizedPathComponents(sourceFile.filename, host.getCurrentDirectory()); + sourcePathComponents.pop(); // FileName is not part of directory + if (commonPathComponents) { + for (var i = 0; i < Math.min(commonPathComponents.length, sourcePathComponents.length); i++) { + if (commonPathComponents[i] !== sourcePathComponents[i]) { + if (i === 0) { + errors.push(createCompilerDiagnostic(Diagnostics.Cannot_find_the_common_subdirectory_path_for_the_input_files)); + return; + } + + // New common path found that is 0 -> i-1 + commonPathComponents.length = i; + break; + } + } + + // If the fileComponent path completely matched and less than already found update the length + if (sourcePathComponents.length < commonPathComponents.length) { + commonPathComponents.length = sourcePathComponents.length; + } + } + else { + // first file + commonPathComponents = sourcePathComponents; + } + } + }); + + commonSourceDirectory = getNormalizedPathFromPathComponents(commonPathComponents); + if (commonSourceDirectory) { + // Make sure directory path ends with directory separator so this string can directly + // used to replace with "" to get the relative path of the source file and the relative path doesn't + // start with / making it rooted path + commonSourceDirectory += directorySeparator; + } + } + } + } +} \ No newline at end of file diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 9853e22d9f..224ea22961 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -1,4 +1,3 @@ -/// /// /// diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index bc4d48b751..d15ad44ca1 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -1,11 +1,4 @@ -/// -/// -/// -/// -/// -/// -/// -/// +/// /// module ts { @@ -298,7 +291,7 @@ module ts { } else { var emitStart = new Date().getTime(); - var emitOutput = checker.emitFiles(); + var emitOutput = program.emitFiles(); var emitErrors = emitOutput.diagnostics; exitStatus = emitOutput.emitResultStatus; var reportStart = new Date().getTime(); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 66a2ff25ee..b164e71d0c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1,5 +1,3 @@ -/// - module ts { export interface Map { [index: string]: T; @@ -914,10 +912,14 @@ module ts { getSourceFiles(): SourceFile[]; getCompilerOptions(): CompilerOptions; getCompilerHost(): CompilerHost; + getDiagnostics(sourceFile?: SourceFile): Diagnostic[]; getGlobalDiagnostics(): Diagnostic[]; + getDeclarationDiagnostics(sourceFile: SourceFile): Diagnostic[]; + getTypeChecker(fullTypeCheckMode: boolean): TypeChecker; getCommonSourceDirectory(): string; + emitFiles(targetSourceFile?: SourceFile): EmitResult; } export interface SourceMapSpan { @@ -959,14 +961,13 @@ module ts { export interface TypeChecker { getProgram(): Program; + getEmitResolver(): EmitResolver; getDiagnostics(sourceFile?: SourceFile): Diagnostic[]; - getDeclarationDiagnostics(sourceFile: SourceFile): Diagnostic[]; getGlobalDiagnostics(): Diagnostic[]; getNodeCount(): number; getIdentifierCount(): number; getSymbolCount(): number; getTypeCount(): number; - emitFiles(targetSourceFile?: SourceFile): EmitResult; getTypeOfSymbolAtLocation(symbol: Symbol, node: Node): Type; getDeclaredTypeOfSymbol(symbol: Symbol): Type; getPropertiesOfType(type: Type): Symbol[]; diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 624b73c68a..2dbbfe877c 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2202,7 +2202,7 @@ module FourSlash { if (errors.length > 0) { throw new Error('Error compiling ' + fileName + ': ' + errors.map(e => e.messageText).join('\r\n')); } - checker.emitFiles(); + program.emitFiles(); result = result || ''; // Might have an empty fourslash file // Compile and execute the test diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 311e2db93e..4e4a01ccef 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -16,8 +16,6 @@ /// /// -/// -/// /// /// /// @@ -1053,7 +1051,7 @@ module Harness { // only emit if there weren't parse errors var emitResult: ts.EmitResult; if (!isEmitBlocked) { - emitResult = checker.emitFiles(); + emitResult = program.emitFiles(); } var errors: HarnessDiagnostic[] = []; diff --git a/src/harness/projectsRunner.ts b/src/harness/projectsRunner.ts index 81791c099d..e22a412b53 100644 --- a/src/harness/projectsRunner.ts +++ b/src/harness/projectsRunner.ts @@ -132,7 +132,7 @@ class ProjectRunner extends RunnerBase { if (!errors.length) { var checker = program.getTypeChecker(/*fullTypeCheck*/ true); errors = checker.getDiagnostics(); - var emitResult = checker.emitFiles(); + var emitResult = program.emitFiles(); errors = ts.concatenate(errors, emitResult.diagnostics); sourceMapData = emitResult.sourceMaps; diff --git a/src/services/services.ts b/src/services/services.ts index 4f9dcaf105..c127947b7f 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1,8 +1,4 @@ -/// -/// -/// -/// -/// +/// /// /// @@ -13,7 +9,6 @@ /// module ts { - export var servicesVersion = "0.4" export interface Node { @@ -2435,7 +2430,7 @@ module ts { var allDiagnostics = checker.getDiagnostics(targetSourceFile); if (compilerOptions.declaration) { // If '-d' is enabled, check for emitter error. One example of emitter error is export class implements non-export interface - allDiagnostics = allDiagnostics.concat(checker.getDeclarationDiagnostics(targetSourceFile)); + allDiagnostics = allDiagnostics.concat(program.getDeclarationDiagnostics(targetSourceFile)); } return allDiagnostics } @@ -4870,7 +4865,7 @@ module ts { // Initialize writer for CompilerHost.writeFile writer = getEmitOutputWriter; - var emitOutput = getFullTypeCheckChecker().emitFiles(sourceFile); + var emitOutput = program.emitFiles(sourceFile); // Reset writer back to undefined to make sure that we produce an error message if CompilerHost.writeFile method is called when we are not in getEmitOutput writer = undefined; From 96c3c90d9ad69eb55443c61b1fbbffe2ff4c6012 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Dec 2014 13:28:38 -0800 Subject: [PATCH 2/8] Rename typechecker parameter related to whether or not we produce diagnostics. Comment what the flag means. --- Jakefile | 2 ++ src/compiler/checker.ts | 66 ++++++++++++++++++++--------------- src/compiler/program.ts | 16 ++++----- src/compiler/types.ts | 10 +++++- src/harness/fourslash.ts | 2 +- src/harness/harness.ts | 2 +- src/harness/projectsRunner.ts | 2 +- src/services/services.ts | 16 ++++----- 8 files changed, 67 insertions(+), 49 deletions(-) diff --git a/Jakefile b/Jakefile index 0425b0a0bf..4ca4cf1960 100644 --- a/Jakefile +++ b/Jakefile @@ -39,6 +39,7 @@ var compilerSources = [ "binder.ts", "checker.ts", "emitter.ts", + "program.ts", "commandLineParser.ts", "tsc.ts", "diagnosticInformationMap.generated.ts" @@ -56,6 +57,7 @@ var servicesSources = [ "binder.ts", "checker.ts", "emitter.ts", + "program.ts", "diagnosticInformationMap.generated.ts" ].map(function (f) { return path.join(compilerDirectory, f); diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8f133d73cd..b9fb09a361 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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, fullTypeCheck: boolean): TypeChecker { + export function createTypeChecker(program: Program, produceDiagnostics: boolean): TypeChecker { var Symbol = objectAllocator.getSymbolConstructor(); var Type = objectAllocator.getTypeConstructor(); var Signature = objectAllocator.getSignatureConstructor(); @@ -4168,7 +4168,7 @@ module ts { } function reportErrorsFromWidening(declaration: Declaration, type: Type) { - if (fullTypeCheck && compilerOptions.noImplicitAny && type.flags & TypeFlags.Unwidened) { + if (produceDiagnostics && compilerOptions.noImplicitAny && type.flags & TypeFlags.Unwidened) { // Report implicit any error within type if possible, otherwise report error on declaration if (!reportWideningErrorsInType(type)) { reportImplicitAnyError(declaration, type); @@ -5957,7 +5957,7 @@ module ts { // Pick the first candidate that matches the arity. This way we can get a contextual type for cases like: // declare function f(a: { xa: number; xb: number; }); // f({ | - if (!fullTypeCheck) { + if (!produceDiagnostics) { for (var i = 0, n = candidates.length; i < n; i++) { if (hasCorrectArity(node, args, candidates[i])) { return candidates[i]; @@ -6254,7 +6254,7 @@ module ts { function checkTypeAssertion(node: TypeAssertion): Type { var exprType = checkExpression(node.expression); var targetType = getTypeFromTypeNode(node.type); - if (fullTypeCheck && targetType !== unknownType) { + if (produceDiagnostics && targetType !== unknownType) { var widenedType = getWidenedType(exprType); if (!(isTypeAssignableTo(targetType, widenedType))) { checkTypeAssignableTo(exprType, targetType, node, Diagnostics.Neither_type_0_nor_type_1_is_assignable_to_the_other); @@ -6340,7 +6340,7 @@ module ts { // must have at least one return statement somewhere in its body. // An exception to this rule is if the function implementation consists of a single 'throw' statement. function checkIfNonVoidFunctionHasReturnExpressionsOrSingleThrowStatment(func: FunctionLikeDeclaration, returnType: Type): void { - if (!fullTypeCheck) { + if (!produceDiagnostics) { return; } @@ -6406,7 +6406,7 @@ module ts { } } - if (fullTypeCheck && node.kind !== SyntaxKind.MethodDeclaration && node.kind !== SyntaxKind.MethodSignature) { + if (produceDiagnostics && node.kind !== SyntaxKind.MethodDeclaration && node.kind !== SyntaxKind.MethodSignature) { checkCollisionWithCapturedSuperVariable(node, (node).name); checkCollisionWithCapturedThisVariable(node,(node).name); } @@ -6813,7 +6813,7 @@ module ts { } function checkAssignmentOperator(valueType: Type): void { - if (fullTypeCheck && operator >= SyntaxKind.FirstAssignment && operator <= SyntaxKind.LastAssignment) { + if (produceDiagnostics && operator >= SyntaxKind.FirstAssignment && operator <= SyntaxKind.LastAssignment) { // TypeScript 1.0 spec (April 2014): 4.17 // An assignment of the form // VarExpr = ValueExpr @@ -6996,7 +6996,7 @@ module ts { function checkTypeParameter(node: TypeParameterDeclaration) { checkSourceElement(node.constraint); - if (fullTypeCheck) { + if (produceDiagnostics) { checkTypeParameterHasIllegalReferencesInConstraint(node); checkTypeNameIsReserved(node.name, Diagnostics.Type_parameter_name_cannot_be_0); } @@ -7025,7 +7025,7 @@ module ts { if (node.type) { checkSourceElement(node.type); } - if (fullTypeCheck) { + if (produceDiagnostics) { checkCollisionWithArgumentsInGeneratedCode(node); if (compilerOptions.noImplicitAny && !node.type) { switch (node.kind) { @@ -7109,7 +7109,7 @@ module ts { return; } - if (!fullTypeCheck) { + if (!produceDiagnostics) { return; } @@ -7177,7 +7177,7 @@ module ts { } function checkAccessorDeclaration(node: AccessorDeclaration) { - if (fullTypeCheck) { + if (produceDiagnostics) { if (node.kind === SyntaxKind.GetAccessor) { if (!isInAmbientContext(node) && node.body && !(bodyContainsAReturnStatement(node.body) || bodyContainsSingleThrowStatement(node.body))) { error(node.name, Diagnostics.A_get_accessor_must_return_a_value_or_consist_of_a_single_throw_statement); @@ -7220,7 +7220,7 @@ module ts { for (var i = 0; i < len; i++) { checkSourceElement(node.typeArguments[i]); var constraint = getConstraintOfTypeParameter((type).target.typeParameters[i]); - if (fullTypeCheck && constraint) { + if (produceDiagnostics && constraint) { var typeArgument = (type).typeArguments[i]; checkTypeAssignableTo(typeArgument, constraint, node, Diagnostics.Type_0_does_not_satisfy_the_constraint_1); } @@ -7234,7 +7234,7 @@ module ts { function checkTypeLiteral(node: TypeLiteralNode) { forEach(node.members, checkSourceElement); - if (fullTypeCheck) { + if (produceDiagnostics) { var type = getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); checkIndexConstraints(type); checkTypeForDuplicateIndexSignatures(node); @@ -7258,7 +7258,7 @@ module ts { } function checkSpecializedSignatureDeclaration(signatureDeclarationNode: SignatureDeclaration): void { - if (!fullTypeCheck) { + if (!produceDiagnostics) { return; } var signature = getSignatureFromDeclaration(signatureDeclarationNode); @@ -7314,7 +7314,7 @@ module ts { } function checkFunctionOrConstructorSymbol(symbol: Symbol): void { - if (!fullTypeCheck) { + if (!produceDiagnostics) { return; } @@ -7524,7 +7524,7 @@ module ts { } function checkExportsOnMergedDeclarations(node: Node): void { - if (!fullTypeCheck) { + if (!produceDiagnostics) { return; } @@ -7599,7 +7599,7 @@ module ts { function checkFunctionDeclaration(node: FunctionDeclaration): void { checkFunctionLikeDeclaration(node); - if (fullTypeCheck) { + if (produceDiagnostics) { checkCollisionWithCapturedSuperVariable(node, node.name); checkCollisionWithCapturedThisVariable(node, node.name); checkCollisionWithRequireExportsInGeneratedCode(node, node.name); @@ -7999,7 +7999,7 @@ module ts { function checkSwitchStatement(node: SwitchStatement) { var expressionType = checkExpression(node.expression); forEach(node.clauses, clause => { - if (fullTypeCheck && clause.kind === SyntaxKind.CaseClause) { + if (produceDiagnostics && clause.kind === SyntaxKind.CaseClause) { var caseClause = clause; // TypeScript 1.0 spec (April 2014):5.9 // In a 'switch' statement, each 'case' expression must be of a type that is assignable to or from the type of the 'switch' expression. @@ -8119,7 +8119,7 @@ module ts { var node = typeParameterDeclarations[i]; checkTypeParameter(node); - if (fullTypeCheck) { + if (produceDiagnostics) { for (var j = 0; j < i; j++) { if (typeParameterDeclarations[j].symbol === node.symbol) { error(node.name, Diagnostics.Duplicate_identifier_0, declarationNameToString(node.name)); @@ -8145,7 +8145,7 @@ module ts { checkTypeReference(baseTypeNode); } if (type.baseTypes.length) { - if (fullTypeCheck) { + if (produceDiagnostics) { var baseType = type.baseTypes[0]; checkTypeAssignableTo(type, baseType, node.name, Diagnostics.Class_0_incorrectly_extends_base_class_1); var staticBaseType = getTypeOfSymbol(baseType.symbol); @@ -8166,7 +8166,7 @@ module ts { if (implementedTypeNodes) { forEach(implementedTypeNodes, typeRefNode => { checkTypeReference(typeRefNode); - if (fullTypeCheck) { + if (produceDiagnostics) { var t = getTypeFromTypeReferenceNode(typeRefNode); if (t !== unknownType) { var declaredType = (t.flags & TypeFlags.Reference) ? (t).target : t; @@ -8182,7 +8182,7 @@ module ts { } forEach(node.members, checkSourceElement); - if (fullTypeCheck) { + if (produceDiagnostics) { checkIndexConstraints(type); checkTypeForDuplicateIndexSignatures(node); } @@ -8335,7 +8335,7 @@ module ts { function checkInterfaceDeclaration(node: InterfaceDeclaration) { checkTypeParameters(node.typeParameters); - if (fullTypeCheck) { + if (produceDiagnostics) { checkTypeNameIsReserved(node.name, Diagnostics.Interface_name_cannot_be_0); checkExportsOnMergedDeclarations(node); @@ -8362,7 +8362,7 @@ module ts { forEach(getInterfaceBaseTypeNodes(node), checkTypeReference); forEach(node.members, checkSourceElement); - if (fullTypeCheck) { + if (produceDiagnostics) { checkTypeForDuplicateIndexSignatures(node); } } @@ -8531,7 +8531,7 @@ module ts { } function checkEnumDeclaration(node: EnumDeclaration) { - if (!fullTypeCheck) { + if (!produceDiagnostics) { return; } @@ -8598,7 +8598,7 @@ module ts { } function checkModuleDeclaration(node: ModuleDeclaration) { - if (fullTypeCheck) { + if (produceDiagnostics) { checkCollisionWithCapturedThisVariable(node, node.name); checkCollisionWithRequireExportsInGeneratedCode(node, node.name); checkExportsOnMergedDeclarations(node); @@ -8919,7 +8919,7 @@ module ts { } function getSortedDiagnostics(): Diagnostic[]{ - Debug.assert(fullTypeCheck, "diagnostics are available only in the full typecheck mode"); + Debug.assert(produceDiagnostics, "diagnostics are available only in the full typecheck mode"); if (diagnosticsModified) { diagnostics.sort(compareDiagnostics); @@ -8929,7 +8929,8 @@ module ts { return diagnostics; } - function getDiagnostics(sourceFile?: SourceFile): Diagnostic[]{ + function getDiagnostics(sourceFile?: SourceFile): Diagnostic[] { + throwIfNonDiagnosticsProducing(); if (sourceFile) { checkSourceFile(sourceFile); return filter(getSortedDiagnostics(), d => d.file === sourceFile); @@ -8938,10 +8939,17 @@ module ts { return getSortedDiagnostics(); } - function getGlobalDiagnostics(): Diagnostic[] { + function getGlobalDiagnostics(): Diagnostic[]{ + throwIfNonDiagnosticsProducing(); return filter(getSortedDiagnostics(), d => !d.file); } + function throwIfNonDiagnosticsProducing() { + if (!produceDiagnostics) { + throw new Error("Trying to get diagnostics from a type checker that does not produce them."); + } + } + // Language service support function isInsideWithStatementBody(node: Node): boolean { diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 7fb77dc9bd..897b9d104b 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -89,15 +89,15 @@ module ts { verifyCompilerOptions(); errors.sort(compareDiagnostics); - var fullTypeChecker: TypeChecker; - var nonFullTypeChecker: TypeChecker; + var diagnosticsProducingTypeChecker: TypeChecker; + var noDiagnosticsTypeChecker: TypeChecker; - function getTypeChecker(full: boolean) { - if (full) { - return fullTypeChecker || (fullTypeChecker = createTypeChecker(program, full)); + function getTypeChecker(produceDiagnostics: boolean) { + if (produceDiagnostics) { + return diagnosticsProducingTypeChecker || (diagnosticsProducingTypeChecker = createTypeChecker(program, produceDiagnostics)); } else { - return nonFullTypeChecker || (nonFullTypeChecker = createTypeChecker(program, full)); + return noDiagnosticsTypeChecker || (noDiagnosticsTypeChecker = createTypeChecker(program, produceDiagnostics)); } } @@ -116,14 +116,14 @@ module ts { return program; function getDeclarationDiagnostics(targetSourceFile: SourceFile): Diagnostic[]{ - var fullTypeChecker = getTypeChecker(/*full:*/ true); + var fullTypeChecker = getTypeChecker(/*produceDiagnostics:*/true); fullTypeChecker.getDiagnostics(targetSourceFile); var resolver = fullTypeChecker.getEmitResolver(); return ts.getDeclarationDiagnostics(program, resolver, targetSourceFile); } function invokeEmitter(targetSourceFile?: SourceFile) { - var resolver = getTypeChecker(/*full:*/ true).getEmitResolver(); + var resolver = getTypeChecker(/*produceDiagnostics:*/true).getEmitResolver(); return emitFiles(resolver, targetSourceFile); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b164e71d0c..7189db3781 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -917,7 +917,15 @@ module ts { getGlobalDiagnostics(): Diagnostic[]; getDeclarationDiagnostics(sourceFile: SourceFile): Diagnostic[]; - getTypeChecker(fullTypeCheckMode: boolean): TypeChecker; + // 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; } diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 2dbbfe877c..14903033ba 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2196,7 +2196,7 @@ module FourSlash { ts.sys.useCaseSensitiveFileNames); // TODO (drosen): We need to enforce checking on these tests. var program = ts.createProgram([Harness.Compiler.fourslashFilename, fileName], { out: "fourslashTestOutput.js", noResolve: true, target: ts.ScriptTarget.ES3 }, host); - var checker = ts.createTypeChecker(program, /*fullTypeCheckMode*/ true); + var checker = ts.createTypeChecker(program, /*produceDiagnostics*/ true); var errors = program.getDiagnostics().concat(checker.getDiagnostics()); if (errors.length > 0) { diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 4e4a01ccef..2885045a36 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -1044,7 +1044,7 @@ module Harness { options.target, useCaseSensitiveFileNames)); - var checker = program.getTypeChecker(/*fullTypeCheckMode*/ true); + var checker = program.getTypeChecker(/*produceDiagnostics*/ true); var isEmitBlocked = checker.isEmitBlocked(); diff --git a/src/harness/projectsRunner.ts b/src/harness/projectsRunner.ts index e22a412b53..52467fdffd 100644 --- a/src/harness/projectsRunner.ts +++ b/src/harness/projectsRunner.ts @@ -130,7 +130,7 @@ class ProjectRunner extends RunnerBase { var errors = program.getDiagnostics(); var sourceMapData: ts.SourceMapData[] = null; if (!errors.length) { - var checker = program.getTypeChecker(/*fullTypeCheck*/ true); + var checker = program.getTypeChecker(/*produceDiagnostics:*/ true); errors = checker.getDiagnostics(); var emitResult = program.emitFiles(); errors = ts.concatenate(errors, emitResult.diagnostics); diff --git a/src/services/services.ts b/src/services/services.ts index c127947b7f..7e8a4b3b6a 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2217,7 +2217,7 @@ module ts { // the sole purpose of this checker is to return semantic diagnostics // creation is deferred - use getFullTypeCheckChecker to get instance - var fullTypeCheckChecker_doNotAccessDirectly: TypeChecker; + var diagnosticsProducingTypeChecker_doNotAccessDirectly: TypeChecker; var useCaseSensitivefilenames = false; var sourceFilesByName: Map = {}; @@ -2239,8 +2239,8 @@ module ts { return lookUp(sourceFilesByName, getCanonicalFileName(filename)); } - function getFullTypeCheckChecker() { - return fullTypeCheckChecker_doNotAccessDirectly || (fullTypeCheckChecker_doNotAccessDirectly = program.getTypeChecker(/*fullTypeCheck*/ true)); + function getDiagnosticsProducingTypeChecker() { + return diagnosticsProducingTypeChecker_doNotAccessDirectly || (diagnosticsProducingTypeChecker_doNotAccessDirectly = program.getTypeChecker(/*produceDiagnostics:*/ true)); } function getRuleProvider(options: FormatCodeOptions) { @@ -2380,8 +2380,8 @@ module ts { // Now create a new compiler program = createProgram(hostfilenames, compilationSettings, createCompilerHost()); - typeInfoResolver = program.getTypeChecker(/*fullTypeCheckMode*/ false); - fullTypeCheckChecker_doNotAccessDirectly = undefined; + typeInfoResolver = program.getTypeChecker(/*produceDiagnostics*/ false); + diagnosticsProducingTypeChecker_doNotAccessDirectly = undefined; } /** @@ -2391,8 +2391,8 @@ module ts { */ function cleanupSemanticCache(): void { if (program) { - typeInfoResolver = program.getTypeChecker(/*fullTypeCheckMode*/ false); - fullTypeCheckChecker_doNotAccessDirectly = undefined; + typeInfoResolver = program.getTypeChecker(/*produceDiagnostics*/ false); + diagnosticsProducingTypeChecker_doNotAccessDirectly = undefined; } } @@ -2421,7 +2421,7 @@ module ts { filename = normalizeSlashes(filename) var compilerOptions = program.getCompilerOptions(); - var checker = getFullTypeCheckChecker(); + var checker = getDiagnosticsProducingTypeChecker(); var targetSourceFile = getSourceFile(filename); // Only perform the action per file regardless of '-out' flag as LanguageServiceHost is expected to call this function per file. From b665323d458247660adaad62072cb1f86a56ee86 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Dec 2014 13:52:47 -0800 Subject: [PATCH 3/8] 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. --- src/compiler/checker.ts | 2 -- src/compiler/emitter.ts | 58 +++++++++++++++++------------------ src/compiler/program.ts | 20 ++++++------ src/compiler/types.ts | 11 +++++-- src/compiler/utilities.ts | 8 ++--- src/harness/compilerRunner.ts | 14 ++++----- src/harness/harness.ts | 4 +-- src/harness/test262Runner.ts | 12 ++++---- src/harness/typeWriter.ts | 9 ++++-- 9 files changed, 73 insertions(+), 65 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b9fb09a361..b01c2df68e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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, diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 1dd0e4cd09..b18881684d 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -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); diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 897b9d104b..b1537da21c 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -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) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 7189db3781..f8cf9a8c83 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -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; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 8e0a02b2be..8aa0481114 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -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); } } diff --git a/src/harness/compilerRunner.ts b/src/harness/compilerRunner.ts index fd49078d04..29861d3e55 100644 --- a/src/harness/compilerRunner.ts +++ b/src/harness/compilerRunner.ts @@ -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 => { diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 2885045a36..0696003c82 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -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'; diff --git a/src/harness/test262Runner.ts b/src/harness/test262Runner.ts index 7e5bafde22..24be77922f 100644 --- a/src/harness/test262Runner.ts +++ b/src/harness/test262Runner.ts @@ -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); }); diff --git a/src/harness/typeWriter.ts b/src/harness/typeWriter.ts index 44888059ff..332173d218 100644 --- a/src/harness/typeWriter.ts +++ b/src/harness/typeWriter.ts @@ -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); From f5ad79fe7a29a23e5784125019d99cccbaa05da4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Dec 2014 14:12:17 -0800 Subject: [PATCH 4/8] 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. --- src/compiler/checker.ts | 37 ++++++++++++------------------------- src/compiler/emitter.ts | 6 +++--- src/compiler/program.ts | 27 +++++++++++++++++++++------ src/compiler/tsc.ts | 2 +- src/compiler/types.ts | 32 ++++++++++++++++++++++++++++++-- src/harness/harness.ts | 2 +- 6 files changed, 68 insertions(+), 38 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b01c2df68e..95f392f036 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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); } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index b18881684d..7b20fb8e21 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -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); } }); diff --git a/src/compiler/program.ts b/src/compiler/program.ts index b1537da21c..8d81ec4567 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -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); } diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index d15ad44ca1..0371b3f97c 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -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 { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f8cf9a8c83..0a00dff6d3 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -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; } diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 0696003c82..e16d165c7a 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -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; From 5a2fb94a955d03847e82a06b9c77ea1328d54f32 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Dec 2014 14:42:58 -0800 Subject: [PATCH 5/8] Clean things up so the services layer can easily emit without having to contort compiler hosts. --- src/compiler/emitter.ts | 37 +++++++++++++++++-------------------- src/compiler/program.ts | 10 ++++++++-- src/compiler/types.ts | 22 ++++++++++++++-------- src/compiler/utilities.ts | 18 ++++++++++++++++-- src/services/services.ts | 15 ++++++--------- 5 files changed, 61 insertions(+), 41 deletions(-) diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 7b20fb8e21..d349a9b626 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -313,8 +313,7 @@ module ts { } function getSourceFilePathInNewDir(sourceFile: SourceFile, host: EmitHost, newDirPath: string) { - var compilerHost = host.getCompilerHost(); - var sourceFilePath = getNormalizedAbsolutePath(sourceFile.filename, compilerHost.getCurrentDirectory()); + var sourceFilePath = getNormalizedAbsolutePath(sourceFile.filename, host.getCurrentDirectory()); sourceFilePath = sourceFilePath.replace(host.getCommonSourceDirectory(), ""); return combinePaths(newDirPath, sourceFilePath); } @@ -331,16 +330,15 @@ module ts { return emitOutputFilePathWithoutExtension + extension; } - function writeFile(compilerHost: CompilerHost, diagnostics: Diagnostic[], filename: string, data: string, writeByteOrderMark: boolean) { - compilerHost.writeFile(filename, data, writeByteOrderMark, hostErrorMessage => { + function writeFile(host: EmitHost, diagnostics: Diagnostic[], filename: string, data: string, writeByteOrderMark: boolean) { + host.writeFile(filename, data, writeByteOrderMark, hostErrorMessage => { diagnostics.push(createCompilerDiagnostic(Diagnostics.Could_not_write_file_0_Colon_1, filename, hostErrorMessage)); }); } function emitDeclarations(host: EmitHost, resolver: EmitResolver, diagnostics: Diagnostic[], jsFilePath: string, root?: SourceFile): DeclarationEmit { - var newLine = host.getCompilerHost().getNewLine(); + var newLine = host.getNewLine(); var compilerOptions = host.getCompilerOptions(); - var compilerHost = host.getCompilerHost(); var write: (s: string) => void; var writeLine: () => void; @@ -1402,8 +1400,8 @@ module ts { declFileName = getRelativePathToDirectoryOrUrl( getDirectoryPath(normalizeSlashes(jsFilePath)), declFileName, - compilerHost.getCurrentDirectory(), - compilerHost.getCanonicalFileName, + host.getCurrentDirectory(), + host.getCanonicalFileName, /*isAbsolutePathAnUrl*/ false); referencePathsOutput += "/// " + newLine; @@ -1464,21 +1462,20 @@ module ts { } } - export function getDeclarationDiagnostics(program: Program, resolver: EmitResolver, targetSourceFile: SourceFile): Diagnostic[] { + export function getDeclarationDiagnostics(host: EmitHost, resolver: EmitResolver, targetSourceFile: SourceFile): Diagnostic[] { var diagnostics: Diagnostic[] = []; - var jsFilePath = getOwnEmitOutputFilePath(targetSourceFile, program, ".js"); - emitDeclarations(program, resolver, diagnostics, jsFilePath, targetSourceFile); + var jsFilePath = getOwnEmitOutputFilePath(targetSourceFile, host, ".js"); + emitDeclarations(host, resolver, diagnostics, jsFilePath, targetSourceFile); return diagnostics; } // 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, 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 = compilerHost.getNewLine(); + var newLine = host.getNewLine(); function emitJavaScript(jsFilePath: string, root?: SourceFile) { var writer = createTextWriter(newLine); @@ -1704,8 +1701,8 @@ module ts { sourceMapData.sourceMapSources.push(getRelativePathToDirectoryOrUrl(sourcesDirectoryPath, node.filename, - compilerHost.getCurrentDirectory(), - compilerHost.getCanonicalFileName, + host.getCurrentDirectory(), + host.getCanonicalFileName, /*isAbsolutePathAnUrl*/ true)); sourceMapSourceIndex = sourceMapData.sourceMapSources.length - 1; @@ -1801,7 +1798,7 @@ module ts { function writeJavaScriptAndSourceMapFile(emitOutput: string, writeByteOrderMark: boolean) { // Write source map file encodeLastRecordedSourceMapSpan(); - writeFile(compilerHost, diagnostics, sourceMapData.sourceMapFilePath, serializeSourceMapContents( + writeFile(host, diagnostics, sourceMapData.sourceMapFilePath, serializeSourceMapContents( 3, sourceMapData.sourceMapFile, sourceMapData.sourceMapSourceRoot, @@ -1849,8 +1846,8 @@ module ts { 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 - compilerHost.getCurrentDirectory(), - compilerHost.getCanonicalFileName, + host.getCurrentDirectory(), + host.getCanonicalFileName, /*isAbsolutePathAnUrl*/ true); } else { @@ -1886,7 +1883,7 @@ module ts { } function writeJavaScriptFile(emitOutput: string, writeByteOrderMark: boolean) { - writeFile(compilerHost, diagnostics, jsFilePath, emitOutput, writeByteOrderMark); + writeFile(host, diagnostics, jsFilePath, emitOutput, writeByteOrderMark); } // Create a temporary variable with a unique unused name. The forLoopVariable parameter signals that the @@ -4128,7 +4125,7 @@ module ts { } }); declarationOutput += emitDeclarationResult.synchronousDeclarationOutput.substring(appliedSyncOutputPos); - writeFile(compilerHost, diagnostics, removeFileExtension(jsFilePath) + ".d.ts", declarationOutput, compilerOptions.emitBOM); + writeFile(host, diagnostics, removeFileExtension(jsFilePath) + ".d.ts", declarationOutput, compilerOptions.emitBOM); } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 8d81ec4567..cc309646c8 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -91,6 +91,7 @@ module ts { var diagnosticsProducingTypeChecker: TypeChecker; var noDiagnosticsTypeChecker: TypeChecker; + var emitHost: EmitHost; program = { getSourceFile: getSourceFile, @@ -104,9 +105,14 @@ module ts { getCommonSourceDirectory: () => commonSourceDirectory, emitFiles: invokeEmitter, isEmitBlocked, + getCurrentDirectory: host.getCurrentDirectory, }; return program; + function getEmitHost() { + return emitHost || (emitHost = createEmitHostFromProgram(program)); + } + function hasEarlyErrors(sourceFile?: SourceFile): boolean { return forEach(getDiagnosticsProducingTypeChecker().getDiagnostics(sourceFile), d => d.isEarly); } @@ -134,12 +140,12 @@ module ts { var typeChecker = getDiagnosticsProducingTypeChecker(); typeChecker.getDiagnostics(targetSourceFile); var resolver = typeChecker.getEmitResolver(); - return ts.getDeclarationDiagnostics(program, resolver, targetSourceFile); + return ts.getDeclarationDiagnostics(getEmitHost(), resolver, targetSourceFile); } function invokeEmitter(targetSourceFile?: SourceFile) { var resolver = getDiagnosticsProducingTypeChecker().getEmitResolver(); - return emitFiles(resolver, program, targetSourceFile); + return emitFiles(resolver, getEmitHost(), targetSourceFile); } function getSourceFile(filename: string) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0a00dff6d3..d606b40c0c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -906,10 +906,8 @@ module ts { identifiers: Map; } - export interface Program { - getSourceFile(filename: string): SourceFile; + export interface Program extends ScriptReferenceHost { getSourceFiles(): SourceFile[]; - getCompilerOptions(): CompilerOptions; getCompilerHost(): CompilerHost; getDiagnostics(sourceFile?: SourceFile): Diagnostic[]; @@ -1102,13 +1100,21 @@ 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; + export interface ScriptReferenceHost { getCompilerOptions(): CompilerOptions; - getCommonSourceDirectory(): string; + getSourceFile(filename: string): SourceFile; + getCurrentDirectory(): string; + } + + export interface EmitHost extends ScriptReferenceHost { + getSourceFiles(): SourceFile[]; isEmitBlocked(sourceFile?: SourceFile): boolean; + + getCommonSourceDirectory(): string; + getCanonicalFileName(fileName: string): string; + getNewLine(): string; + + writeFile(filename: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void): void; } export interface EmitResolver { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 8aa0481114..1b9d9caf32 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -636,10 +636,10 @@ module ts { return undefined; } - export function tryResolveScriptReference(host: EmitHost, sourceFile: SourceFile, reference: FileReference) { + export function tryResolveScriptReference(host: ScriptReferenceHost, sourceFile: SourceFile, reference: FileReference) { if (!host.getCompilerOptions().noResolve) { var referenceFileName = isRootedDiskPath(reference.filename) ? reference.filename : combinePaths(getDirectoryPath(sourceFile.filename), reference.filename); - referenceFileName = getNormalizedAbsolutePath(referenceFileName, host.getCompilerHost().getCurrentDirectory()); + referenceFileName = getNormalizedAbsolutePath(referenceFileName, host.getCurrentDirectory()); return host.getSourceFile(referenceFileName); } } @@ -734,4 +734,18 @@ module ts { return false; } + export function createEmitHostFromProgram(program: Program): EmitHost { + var compilerHost = program.getCompilerHost(); + return { + getCanonicalFileName: compilerHost.getCanonicalFileName, + getCommonSourceDirectory: program.getCommonSourceDirectory, + getCompilerOptions: program.getCompilerOptions, + getCurrentDirectory: compilerHost.getCurrentDirectory, + getNewLine: compilerHost.getNewLine, + getSourceFile: program.getSourceFile, + getSourceFiles: program.getSourceFiles, + isEmitBlocked: program.isEmitBlocked, + writeFile: compilerHost.writeFile, + }; + } } \ No newline at end of file diff --git a/src/services/services.ts b/src/services/services.ts index 7e8a4b3b6a..adc1537859 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2224,7 +2224,6 @@ module ts { var documentRegistry = documentRegistry; var cancellationToken = new CancellationTokenObject(host.getCancellationToken && host.getCancellationToken()); var activeCompletionSession: CompletionSession; // The current active completion session, used to get the completion entry details - var writer: (filename: string, data: string, writeByteOrderMark: boolean) => void = undefined; // Check if the localized messages json is set, otherwise query the host for it if (!localizedDiagnosticMessages && host.getLocalizedDiagnosticMessages) { @@ -2267,7 +2266,6 @@ module ts { return host.getDefaultLibFilename(options); }, writeFile: (filename, data, writeByteOrderMark) => { - writer(filename, data, writeByteOrderMark); }, getCurrentDirectory: (): string => { return host.getCurrentDirectory(); @@ -4854,7 +4852,7 @@ module ts { var outputFiles: OutputFile[] = []; - function getEmitOutputWriter(filename: string, data: string, writeByteOrderMark: boolean) { + function writeFile(filename: string, data: string, writeByteOrderMark: boolean) { outputFiles.push({ name: filename, writeByteOrderMark: writeByteOrderMark, @@ -4862,13 +4860,12 @@ module ts { }); } - // Initialize writer for CompilerHost.writeFile - writer = getEmitOutputWriter; + // Get an emit host from our program, but override the writeFile functionality to + // call our local writer function. + var emitHost = createEmitHostFromProgram(program); + emitHost.writeFile = writeFile; - var emitOutput = program.emitFiles(sourceFile); - - // Reset writer back to undefined to make sure that we produce an error message if CompilerHost.writeFile method is called when we are not in getEmitOutput - writer = undefined; + var emitOutput = emitFiles(getDiagnosticsProducingTypeChecker().getEmitResolver(), emitHost, sourceFile); return { outputFiles, From 94d576290e7d627f2db26cbcb09388101b0e66c3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Dec 2014 15:10:15 -0800 Subject: [PATCH 6/8] Don't expose EmitHost. it is only used by a non-exposed function. --- Jakefile | 1 + src/compiler/checker.ts | 2 +- src/compiler/emitter.ts | 11 +++++++++++ src/compiler/types.ts | 41 ++++++----------------------------------- 4 files changed, 19 insertions(+), 36 deletions(-) diff --git a/Jakefile b/Jakefile index 4ca4cf1960..433be9509c 100644 --- a/Jakefile +++ b/Jakefile @@ -94,6 +94,7 @@ var definitionsRoots = [ "compiler/scanner.d.ts", "compiler/parser.d.ts", "compiler/checker.d.ts", + "compiler/program.d.ts", "services/services.d.ts", ]; diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 95f392f036..e7bc88f466 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3,7 +3,7 @@ module ts { var nextSymbolId = 1; var nextNodeId = 1; - var nextMergeId = 1; + var nextMergeId = 1; /// fullTypeCheck denotes if this instance of the typechecker will be used to get semantic diagnostics. /// If fullTypeCheck === true, then the typechecker should do every possible check to produce all errors diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index d349a9b626..621bf3c18a 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1462,6 +1462,17 @@ module ts { } } + export interface EmitHost extends ScriptReferenceHost { + getSourceFiles(): SourceFile[]; + isEmitBlocked(sourceFile?: SourceFile): boolean; + + getCommonSourceDirectory(): string; + getCanonicalFileName(fileName: string): string; + getNewLine(): string; + + writeFile(filename: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void): void; + } + export function getDeclarationDiagnostics(host: EmitHost, resolver: EmitResolver, targetSourceFile: SourceFile): Diagnostic[] { var diagnostics: Diagnostic[] = []; var jsFilePath = getOwnEmitOutputFilePath(targetSourceFile, host, ".js"); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index d606b40c0c..05f57ad821 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -906,6 +906,12 @@ module ts { identifiers: Map; } + export interface ScriptReferenceHost { + getCompilerOptions(): CompilerOptions; + getSourceFile(filename: string): SourceFile; + getCurrentDirectory(): string; + } + export interface Program extends ScriptReferenceHost { getSourceFiles(): SourceFile[]; getCompilerHost(): CompilerHost; @@ -972,24 +978,6 @@ module ts { 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 { @@ -1100,23 +1088,6 @@ module ts { errorModuleName?: string // If the symbol is not visible from module, module's name } - export interface ScriptReferenceHost { - getCompilerOptions(): CompilerOptions; - getSourceFile(filename: string): SourceFile; - getCurrentDirectory(): string; - } - - export interface EmitHost extends ScriptReferenceHost { - getSourceFiles(): SourceFile[]; - isEmitBlocked(sourceFile?: SourceFile): boolean; - - getCommonSourceDirectory(): string; - getCanonicalFileName(fileName: string): string; - getNewLine(): string; - - writeFile(filename: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void): void; - } - export interface EmitResolver { getLocalNameOfContainer(container: ModuleDeclaration | EnumDeclaration): string; getExpressionNamePrefix(node: Identifier): string; From 71c82dd33fcfc998136cad7bcb254a27aa15d523 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Dec 2014 15:32:56 -0800 Subject: [PATCH 7/8] Remove obsolete comment. --- src/compiler/program.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index cc309646c8..f57882411c 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -2,7 +2,6 @@ /// module ts { - // TODO (drosen, mhegazy): Move to a more appropriate file. export function createCompilerHost(options: CompilerOptions): CompilerHost { var currentDirectory: string; var existingDirectories: Map = {}; From b155351e0a3592d373a38bd72339044d750f78a6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Dec 2014 17:06:12 -0800 Subject: [PATCH 8/8] Remove unnecessary capture of the diagnostics type checker. --- src/services/services.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index d36dc9bc57..e50f8c180d 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1920,10 +1920,6 @@ module ts { // this checker is used to answer all LS questions except errors var typeInfoResolver: TypeChecker; - // the sole purpose of this checker is to return semantic diagnostics - // creation is deferred - use getFullTypeCheckChecker to get instance - var diagnosticsProducingTypeChecker_doNotAccessDirectly: TypeChecker; - var useCaseSensitivefilenames = false; var sourceFilesByName: Map = {}; var documentRegistry = documentRegistry; @@ -1944,7 +1940,7 @@ module ts { } function getDiagnosticsProducingTypeChecker() { - return diagnosticsProducingTypeChecker_doNotAccessDirectly || (diagnosticsProducingTypeChecker_doNotAccessDirectly = program.getTypeChecker(/*produceDiagnostics:*/ true)); + return program.getTypeChecker(/*produceDiagnostics:*/ true); } function getRuleProvider(options: FormatCodeOptions) { @@ -2084,7 +2080,6 @@ module ts { // Now create a new compiler program = createProgram(hostfilenames, compilationSettings, createCompilerHost()); typeInfoResolver = program.getTypeChecker(/*produceDiagnostics*/ false); - diagnosticsProducingTypeChecker_doNotAccessDirectly = undefined; } /** @@ -2095,7 +2090,6 @@ module ts { function cleanupSemanticCache(): void { if (program) { typeInfoResolver = program.getTypeChecker(/*produceDiagnostics*/ false); - diagnosticsProducingTypeChecker_doNotAccessDirectly = undefined; } }