/// /// /// /// /// /// /// /// /// module ts { var version = "1.1.0.0"; /** * Checks to see if the locale is in the appropriate format, * and if it is, attempts to set the appropriate language. */ function validateLocaleAndSetLanguage(locale: string, errors: Diagnostic[]): boolean { var matchResult = /^([a-z]+)([_\-]([a-z]+))?$/.exec(locale.toLowerCase()); if (!matchResult) { errors.push(createCompilerDiagnostic(Diagnostics.Locale_must_be_of_the_form_language_or_language_territory_For_example_0_or_1, 'en', 'ja-jp')); return false; } var language = matchResult[1]; var territory = matchResult[3]; // First try the entire locale, then fall back to just language if that's all we have. if (!trySetLanguageAndTerritory(language, territory, errors) && !trySetLanguageAndTerritory(language, undefined, errors)) { errors.push(createCompilerDiagnostic(Diagnostics.Unsupported_locale_0, locale)); return false; } return true; } function trySetLanguageAndTerritory(language: string, territory: string, errors: Diagnostic[]): boolean { var compilerFilePath = normalizePath(sys.getExecutingFilePath()); var containingDirectoryPath = getDirectoryPath(compilerFilePath); var filePath = combinePaths(containingDirectoryPath, language); if (territory) { filePath = filePath + "-" + territory; } filePath = sys.resolvePath(combinePaths(filePath, "diagnosticMessages.generated.json")); if (!sys.fileExists(filePath)) { return false; } // TODO: Add codePage support for readFile? try { var fileContents = sys.readFile(filePath); } catch (e) { errors.push(createCompilerDiagnostic(Diagnostics.Unable_to_open_file_0, filePath)); return false; } try { ts.localizedDiagnosticMessages = JSON.parse(fileContents); } catch (e) { errors.push(createCompilerDiagnostic(Diagnostics.Corrupted_locale_file_0, filePath)); return false; } return true; } function countLines(program: Program): number { var count = 0; forEach(program.getSourceFiles(), file => { count += file.getLineAndCharacterFromPosition(file.end).line; }); return count; } function getDiagnosticText(message: DiagnosticMessage, ...args: any[]): string { var diagnostic: Diagnostic = createCompilerDiagnostic.apply(undefined, arguments); return diagnostic.messageText; } function reportDiagnostic(diagnostic: Diagnostic) { var output = ""; if (diagnostic.file) { var loc = diagnostic.file.getLineAndCharacterFromPosition(diagnostic.start); output += diagnostic.file.filename + "(" + loc.line + "," + loc.character + "): "; } var category = DiagnosticCategory[diagnostic.category].toLowerCase(); output += category + " TS" + diagnostic.code + ": " + diagnostic.messageText + sys.newLine; sys.write(output); } function reportDiagnostics(diagnostics: Diagnostic[]) { for (var i = 0; i < diagnostics.length; i++) { reportDiagnostic(diagnostics[i]); } } function padLeft(s: string, length: number) { while (s.length < length) { s = " " + s; } return s; } function padRight(s: string, length: number) { while (s.length < length) { s = s + " "; } return s; } function reportStatisticalValue(name: string, value: string) { sys.write(padRight(name + ":", 12) + padLeft(value.toString(), 10) + sys.newLine); } function reportCountStatistic(name: string, count: number) { reportStatisticalValue(name, "" + count); } function reportTimeStatistic(name: string, time: number) { reportStatisticalValue(name, (time / 1000).toFixed(2) + "s"); } 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(); } 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.message); } text = ""; } return text !== undefined ? createSourceFile(filename, text, languageVersion, /*version:*/ "0") : 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: getSourceFile, getDefaultLibFilename: () => combinePaths(getDirectoryPath(normalizePath(sys.getExecutingFilePath())), "lib.d.ts"), writeFile: writeFile, getCurrentDirectory: () => currentDirectory || (currentDirectory = sys.getCurrentDirectory()), useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames, getCanonicalFileName: getCanonicalFileName, getNewLine: () => sys.newLine }; } export function executeCommandLine(args: string[]): void { var commandLine = parseCommandLine(args); var compilerOptions = commandLine.options; if (compilerOptions.locale) { if (typeof JSON === "undefined") { reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--locale")); return sys.exit(1); } validateLocaleAndSetLanguage(commandLine.options.locale, commandLine.errors); } // If there are any errors due to command line parsing and/or // setting up localization, report them and quit. if (commandLine.errors.length > 0) { reportDiagnostics(commandLine.errors); return sys.exit(EmitReturnStatus.CompilerOptionsErrors); } if (compilerOptions.version) { reportDiagnostic(createCompilerDiagnostic(Diagnostics.Version_0, version)); return sys.exit(EmitReturnStatus.Succeeded); } if (compilerOptions.help) { printVersion(); printHelp(); return sys.exit(EmitReturnStatus.Succeeded); } if (commandLine.filenames.length === 0) { printVersion(); printHelp(); return sys.exit(EmitReturnStatus.CompilerOptionsErrors); } var defaultCompilerHost = createCompilerHost(compilerOptions); if (compilerOptions.watch) { if (!sys.watchFile) { reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch")); return sys.exit(EmitReturnStatus.CompilerOptionsErrors); } watchProgram(commandLine, defaultCompilerHost); } else { var result = compile(commandLine, defaultCompilerHost).exitStatus return sys.exit(result); } } /** * Compiles the program once, and then watches all given and referenced files for changes. * Upon detecting a file change, watchProgram will queue up file modification events for the next * 250ms and then perform a recompilation. The reasoning is that in some cases, an editor can * save all files at once, and we'd like to just perform a single recompilation. */ function watchProgram(commandLine: ParsedCommandLine, compilerHost: CompilerHost): void { var watchers: Map = {}; var updatedFiles: Map = {}; // Compile the program the first time and watch all given/referenced files. var program = compile(commandLine, compilerHost).program; reportDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes)); addWatchers(program); return; function addWatchers(program: Program) { forEach(program.getSourceFiles(), f => { var filename = getCanonicalName(f.filename); watchers[filename] = sys.watchFile(filename, fileUpdated); }); } function removeWatchers(program: Program) { forEach(program.getSourceFiles(), f => { var filename = getCanonicalName(f.filename); if (hasProperty(watchers, filename)) { watchers[filename].close(); } }); watchers = {}; } // Fired off whenever a file is changed. function fileUpdated(filename: string) { var firstNotification = isEmpty(updatedFiles); updatedFiles[getCanonicalName(filename)] = true; // Only start this off when the first file change comes in, // so that we can batch up all further changes. if (firstNotification) { setTimeout(() => { var changedFiles = updatedFiles; updatedFiles = {}; recompile(changedFiles); }, 250); } } function recompile(changedFiles: Map) { reportDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Compiling)); // Remove all the watchers, as we may not be watching every file // specified since the last compilation cycle. removeWatchers(program); // Reuse source files from the last compilation so long as they weren't changed. var oldSourceFiles = arrayToMap( filter(program.getSourceFiles(), file => !hasProperty(changedFiles, getCanonicalName(file.filename))), file => getCanonicalName(file.filename)); // We create a new compiler host for this compilation cycle. // This new host is effectively the same except that 'getSourceFile' // will try to reuse the SourceFiles from the last compilation cycle // so long as they were not modified. var newCompilerHost = clone(compilerHost); newCompilerHost.getSourceFile = (fileName, languageVersion, onError) => { fileName = getCanonicalName(fileName); var sourceFile = lookUp(oldSourceFiles, fileName); if (sourceFile) { return sourceFile; } return compilerHost.getSourceFile(fileName, languageVersion, onError); }; program = compile(commandLine, newCompilerHost).program; reportDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes)); addWatchers(program); } function getCanonicalName(fileName: string) { return compilerHost.getCanonicalFileName(fileName); } } function compile(commandLine: ParsedCommandLine, compilerHost: CompilerHost) { var parseStart = new Date().getTime(); var compilerOptions = commandLine.options; var program = createProgram(commandLine.filenames, compilerOptions, compilerHost); var bindStart = new Date().getTime(); var errors: Diagnostic[] = program.getDiagnostics(); var exitStatus: EmitReturnStatus; if (errors.length) { var checkStart = bindStart; var emitStart = bindStart; var reportStart = bindStart; exitStatus = EmitReturnStatus.AllOutputGenerationSkipped; } else { var checker = program.getTypeChecker(/*fullTypeCheckMode*/ true); var checkStart = new Date().getTime(); var semanticErrors = checker.getDiagnostics(); var emitStart = new Date().getTime(); var emitOutput = checker.emitFiles(); var emitErrors = emitOutput.errors; exitStatus = emitOutput.emitResultStatus; var reportStart = new Date().getTime(); errors = concatenate(semanticErrors, emitErrors); } reportDiagnostics(errors); if (commandLine.options.diagnostics) { var memoryUsed = sys.getMemoryUsage ? sys.getMemoryUsage() : -1; reportCountStatistic("Files", program.getSourceFiles().length); reportCountStatistic("Lines", countLines(program)); reportCountStatistic("Nodes", checker ? checker.getNodeCount() : 0); reportCountStatistic("Identifiers", checker ? checker.getIdentifierCount() : 0); reportCountStatistic("Symbols", checker ? checker.getSymbolCount() : 0); reportCountStatistic("Types", checker ? checker.getTypeCount() : 0); if (memoryUsed >= 0) { reportStatisticalValue("Memory used", Math.round(memoryUsed / 1000) + "K"); } reportTimeStatistic("Parse time", bindStart - parseStart); reportTimeStatistic("Bind time", checkStart - bindStart); reportTimeStatistic("Check time", emitStart - checkStart); reportTimeStatistic("Emit time", reportStart - emitStart); reportTimeStatistic("Total time", reportStart - parseStart); } return { program: program, exitStatus: exitStatus } } function printVersion() { sys.write(getDiagnosticText(Diagnostics.Version_0, version) + sys.newLine); } function printHelp() { var output = ""; // We want to align our "syntax" and "examples" commands to a certain margin. var syntaxLength = getDiagnosticText(Diagnostics.Syntax_Colon_0, "").length; var examplesLength = getDiagnosticText(Diagnostics.Examples_Colon_0, "").length; var marginLength = Math.max(syntaxLength, examplesLength); // Build up the syntactic skeleton. var syntax = makePadding(marginLength - syntaxLength); syntax += "tsc [" + getDiagnosticText(Diagnostics.options) + "] [" + getDiagnosticText(Diagnostics.file) + " ...]"; output += getDiagnosticText(Diagnostics.Syntax_Colon_0, syntax); output += sys.newLine + sys.newLine; // Build up the list of examples. var padding = makePadding(marginLength); output += getDiagnosticText(Diagnostics.Examples_Colon_0, makePadding(marginLength - examplesLength) + "tsc hello.ts") + sys.newLine; output += padding + "tsc --out foo.js foo.ts" + sys.newLine; output += padding + "tsc @args.txt" + sys.newLine; output += sys.newLine; output += getDiagnosticText(Diagnostics.Options_Colon) + sys.newLine; // Sort our options by their names, (e.g. "--noImplicitAny" comes before "--watch") var optsList = optionDeclarations.slice(); optsList.sort((a, b) => compareValues(a.name.toLowerCase(), b.name.toLowerCase())); // We want our descriptions to align at the same column in our output, // so we keep track of the longest option usage string. var marginLength = 0; var usageColumn: string[] = []; // Things like "-d, --declaration" go in here. var descriptionColumn: string[] = []; for (var i = 0; i < optsList.length; i++) { var option = optsList[i]; // If an option lacks a description, // it is not officially supported. if (!option.description) { continue; } var usageText = " "; if (option.shortName) { usageText += "-" + option.shortName; usageText += getParamName(option); usageText += ", "; } usageText += "--" + option.name; usageText += getParamName(option); usageColumn.push(usageText); descriptionColumn.push(getDiagnosticText(option.description)); // Set the new margin for the description column if necessary. marginLength = Math.max(usageText.length, marginLength); } // Special case that can't fit in the loop. var usageText = " @<" + getDiagnosticText(Diagnostics.file) + ">"; usageColumn.push(usageText); descriptionColumn.push(getDiagnosticText(Diagnostics.Insert_command_line_options_and_files_from_a_file)); marginLength = Math.max(usageText.length, marginLength); // Print out each row, aligning all the descriptions on the same column. for (var i = 0; i < usageColumn.length; i++) { var usage = usageColumn[i]; var description = descriptionColumn[i]; output += usage + makePadding(marginLength - usage.length + 2) + description + sys.newLine; } sys.write(output); return; function getParamName(option: CommandLineOption) { if (option.paramName !== undefined) { return " " + getDiagnosticText(option.paramName); } return ""; } function makePadding(paddingLength: number): string { return Array(paddingLength + 1).join(" "); } } } ts.executeCommandLine(sys.args);