///
///
///
///
///
///
///
///
///
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);