Merge pull request #324 from Microsoft/watcherIardlyKnowEr
Support the '--watch' compiler flag.
This commit is contained in:
commit
9a89147587
8 changed files with 186 additions and 38 deletions
|
@ -11,9 +11,10 @@ module ts {
|
|||
"o": "out",
|
||||
"t": "target",
|
||||
"v": "version",
|
||||
"w": "watch",
|
||||
};
|
||||
|
||||
var options: CommandLineOption[] = [
|
||||
var optionDeclarations: CommandLineOption[] = [
|
||||
{ name: "charset", type: "string" },
|
||||
{ name: "codepage", type: "number" },
|
||||
{ name: "declaration", type: "boolean" },
|
||||
|
@ -32,14 +33,15 @@ module ts {
|
|||
{ name: "sourceMap", type: "boolean" },
|
||||
{ name: "sourceRoot", type: "string" },
|
||||
{ name: "target", type: { "es3": ScriptTarget.ES3, "es5": ScriptTarget.ES5 }, error: Diagnostics.Argument_for_target_option_must_be_es3_or_es5 },
|
||||
{ name: "version", type: "boolean" }
|
||||
{ name: "version", type: "boolean" },
|
||||
{ name: "watch", type: "boolean" },
|
||||
];
|
||||
|
||||
// Map command line switches to compiler options' property descriptors. Keys must be lower case spellings of command line switches.
|
||||
// The 'name' property specifies the property name in the CompilerOptions type. The 'type' property specifies the type of the option.
|
||||
var optionDeclarations: Map<CommandLineOption> = {};
|
||||
forEach(options, option => {
|
||||
optionDeclarations[option.name.toLowerCase()] = option;
|
||||
var optionMap: Map<CommandLineOption> = {};
|
||||
forEach(optionDeclarations, option => {
|
||||
optionMap[option.name.toLowerCase()] = option;
|
||||
});
|
||||
|
||||
export function parseCommandLine(commandLine: string[]): ParsedCommandLine {
|
||||
|
@ -73,8 +75,8 @@ module ts {
|
|||
s = shortOptionNames[s];
|
||||
}
|
||||
|
||||
if (hasProperty(optionDeclarations, s)) {
|
||||
var opt = optionDeclarations[s];
|
||||
if (hasProperty(optionMap, s)) {
|
||||
var opt = optionMap[s];
|
||||
|
||||
// Check to see if no argument was provided (e.g. "--locale" is the last command-line argument).
|
||||
if (!args[i] && opt.type !== "boolean") {
|
||||
|
|
|
@ -114,7 +114,11 @@ module ts {
|
|||
}
|
||||
|
||||
export function isEmpty<T>(map: Map<T>) {
|
||||
for (var id in map) return false;
|
||||
for (var id in map) {
|
||||
if (hasProperty(map, id)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -209,6 +209,7 @@ module ts {
|
|||
Class_0_defines_instance_member_function_1_but_extended_class_2_defines_it_as_instance_member_property: { code: 4019, category: DiagnosticCategory.NoPrefix, key: "Class '{0}' defines instance member function '{1}', but extended class '{2}' defines it as instance member property." },
|
||||
In_an_enum_with_multiple_declarations_only_one_declaration_can_omit_an_initializer_for_its_first_enum_element: { code: 4024, category: DiagnosticCategory.Error, key: "In an enum with multiple declarations, only one declaration can omit an initializer for its first enum element." },
|
||||
Named_properties_0_of_types_1_and_2_are_not_identical: { code: 4032, category: DiagnosticCategory.NoPrefix, key: "Named properties '{0}' of types '{1}' and '{2}' are not identical." },
|
||||
The_current_host_does_not_support_the_0_option: { code: 5001, category: DiagnosticCategory.Error, key: "The current host does not support the '{0}' option." },
|
||||
Cannot_find_the_common_subdirectory_path_for_the_input_files: { code: 5009, category: DiagnosticCategory.Error, key: "Cannot find the common subdirectory path for the input files." },
|
||||
Cannot_read_file_0_Colon_1: { code: 5012, category: DiagnosticCategory.Error, key: "Cannot read file '{0}': {1}" },
|
||||
Unsupported_file_encoding: { code: 5013, category: DiagnosticCategory.NoPrefix, key: "Unsupported file encoding." },
|
||||
|
@ -216,6 +217,8 @@ module ts {
|
|||
Option_mapRoot_cannot_be_specified_without_specifying_sourcemap_option: { code: 5038, category: DiagnosticCategory.Error, key: "Option mapRoot cannot be specified without specifying sourcemap option." },
|
||||
Option_sourceRoot_cannot_be_specified_without_specifying_sourcemap_option: { code: 5039, category: DiagnosticCategory.Error, key: "Option sourceRoot cannot be specified without specifying sourcemap option." },
|
||||
Version_0: { code: 6029, category: DiagnosticCategory.Message, key: "Version {0}" },
|
||||
File_change_detected_Compiling: { code: 6032, category: DiagnosticCategory.Message, key: "File change detected. Compiling..." },
|
||||
Compilation_complete_Watching_for_file_changes: { code: 6042, category: DiagnosticCategory.Message, key: "Compilation complete. Watching for file changes." },
|
||||
Variable_0_implicitly_has_an_1_type: { code: 7005, category: DiagnosticCategory.Error, key: "Variable '{0}' implicitly has an '{1}' type." },
|
||||
Parameter_0_implicitly_has_an_1_type: { code: 7006, category: DiagnosticCategory.Error, key: "Parameter '{0}' implicitly has an '{1}' type." },
|
||||
Member_0_implicitly_has_an_1_type: { code: 7008, category: DiagnosticCategory.Error, key: "Member '{0}' implicitly has an '{1}' type." },
|
||||
|
|
|
@ -830,6 +830,10 @@
|
|||
"category": "NoPrefix",
|
||||
"code": 4032
|
||||
},
|
||||
"The current host does not support the '{0}' option.": {
|
||||
"category": "Error",
|
||||
"code": 5001
|
||||
},
|
||||
"Cannot find the common subdirectory path for the input files.": {
|
||||
"category": "Error",
|
||||
"code": 5009
|
||||
|
@ -854,12 +858,18 @@
|
|||
"category": "Error",
|
||||
"code": 5039
|
||||
},
|
||||
|
||||
"Version {0}": {
|
||||
"category": "Message",
|
||||
"code": 6029
|
||||
},
|
||||
|
||||
},
|
||||
"File change detected. Compiling...": {
|
||||
"category": "Message",
|
||||
"code": 6032
|
||||
},
|
||||
"Compilation complete. Watching for file changes.": {
|
||||
"category": "Message",
|
||||
"code": 6042
|
||||
},
|
||||
"Variable '{0}' implicitly has an '{1}' type.": {
|
||||
"category": "Error",
|
||||
"code": 7005
|
||||
|
|
|
@ -3527,7 +3527,6 @@ module ts {
|
|||
}
|
||||
|
||||
export function createProgram(rootNames: string[], options: CompilerOptions, host: CompilerHost): Program {
|
||||
|
||||
var program: Program;
|
||||
var files: SourceFile[] = [];
|
||||
var filesByName: Map<SourceFile> = {};
|
||||
|
@ -3536,7 +3535,9 @@ module ts {
|
|||
var commonSourceDirectory: string;
|
||||
|
||||
forEach(rootNames, name => processRootFile(name, false));
|
||||
if (!seenNoDefaultLib) processRootFile(host.getDefaultLibFilename(), true);
|
||||
if (!seenNoDefaultLib) {
|
||||
processRootFile(host.getDefaultLibFilename(), true);
|
||||
}
|
||||
verifyCompilerOptions();
|
||||
errors.sort(compareDiagnostics);
|
||||
program = {
|
||||
|
@ -3627,7 +3628,7 @@ module ts {
|
|||
|
||||
function processReferencedFiles(file: SourceFile, basePath: string) {
|
||||
forEach(file.referencedFiles, ref => {
|
||||
processSourceFile(normalizePath(combinePaths(basePath, ref.filename)), false, file, ref.pos, ref.end);
|
||||
processSourceFile(normalizePath(combinePaths(basePath, ref.filename)), /* isDefaultLib */ false, file, ref.pos, ref.end);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -3640,9 +3641,14 @@ module ts {
|
|||
var searchPath = basePath;
|
||||
while (true) {
|
||||
var searchName = normalizePath(combinePaths(searchPath, moduleName));
|
||||
if (findModuleSourceFile(searchName + ".ts", nameLiteral) || findModuleSourceFile(searchName + ".d.ts", nameLiteral)) break;
|
||||
if (findModuleSourceFile(searchName + ".ts", nameLiteral) || findModuleSourceFile(searchName + ".d.ts", nameLiteral)) {
|
||||
break;
|
||||
}
|
||||
|
||||
var parentPath = getDirectoryPath(searchPath);
|
||||
if (parentPath === searchPath) break;
|
||||
if (parentPath === searchPath) {
|
||||
break;
|
||||
}
|
||||
searchPath = parentPath;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,9 +5,9 @@ interface System {
|
|||
newLine: string;
|
||||
useCaseSensitiveFileNames: boolean;
|
||||
write(s: string): void;
|
||||
writeErr(s: string): void;
|
||||
readFile(fileName: string, encoding?: string): string;
|
||||
writeFile(fileName: string, data: string): void;
|
||||
watchFile?(fileName: string, callback: (fileName: string) => void): FileWatcher;
|
||||
resolvePath(path: string): string;
|
||||
fileExists(path: string): boolean;
|
||||
directoryExists(path: string): boolean;
|
||||
|
@ -18,6 +18,10 @@ interface System {
|
|||
exit(exitCode?: number): void;
|
||||
}
|
||||
|
||||
interface FileWatcher {
|
||||
close(): void;
|
||||
}
|
||||
|
||||
declare var require: any;
|
||||
declare var module: any;
|
||||
declare var process: any;
|
||||
|
@ -187,6 +191,22 @@ var sys: System = (function () {
|
|||
},
|
||||
readFile: readFile,
|
||||
writeFile: writeFile,
|
||||
watchFile: (fileName, callback) => {
|
||||
// watchFile polls a file every 250ms, picking up file notifications.
|
||||
_fs.watchFile(fileName, { persistent: true, interval: 250 }, fileChanged);
|
||||
|
||||
return {
|
||||
close() { _fs.unwatchFile(fileName, fileChanged); }
|
||||
};
|
||||
|
||||
function fileChanged(curr: any, prev: any) {
|
||||
if (+curr.mtime <= +prev.mtime) {
|
||||
return;
|
||||
}
|
||||
|
||||
callback(fileName);
|
||||
};
|
||||
},
|
||||
resolvePath: function (path: string): string {
|
||||
return _path.resolve(path);
|
||||
},
|
||||
|
|
|
@ -81,10 +81,10 @@ module ts {
|
|||
function reportDiagnostic(error: Diagnostic) {
|
||||
if (error.file) {
|
||||
var loc = error.file.getLineAndCharacterFromPosition(error.start);
|
||||
sys.writeErr(error.file.filename + "(" + loc.line + "," + loc.character + "): " + error.messageText + sys.newLine);
|
||||
sys.write(error.file.filename + "(" + loc.line + "," + loc.character + "): " + error.messageText + sys.newLine);
|
||||
}
|
||||
else {
|
||||
sys.writeErr(error.messageText + sys.newLine);
|
||||
sys.write(error.messageText + sys.newLine);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,7 +110,7 @@ module ts {
|
|||
}
|
||||
|
||||
function reportStatisticalValue(name: string, value: string) {
|
||||
sys.writeErr(padRight(name + ":", 12) + padLeft(value.toString(), 10) + sys.newLine);
|
||||
sys.write(padRight(name + ":", 12) + padLeft(value.toString(), 10) + sys.newLine);
|
||||
}
|
||||
|
||||
function reportCountStatistic(name: string, count: number) {
|
||||
|
@ -179,33 +179,133 @@ module ts {
|
|||
};
|
||||
}
|
||||
|
||||
export function executeCommandLine(args: string[]): number {
|
||||
var cmds = parseCommandLine(args);
|
||||
export function executeCommandLine(args: string[]): void {
|
||||
var commandLine = parseCommandLine(args);
|
||||
|
||||
if (cmds.options.locale) {
|
||||
validateLocaleAndSetLanguage(cmds.options.locale, cmds.errors);
|
||||
if (commandLine.options.locale) {
|
||||
validateLocaleAndSetLanguage(commandLine.options.locale, commandLine.errors);
|
||||
}
|
||||
|
||||
if (cmds.filenames.length === 0 && !(cmds.options.help || cmds.options.version)) {
|
||||
cmds.errors.push(createCompilerDiagnostic(Diagnostics.No_input_files_specified));
|
||||
}
|
||||
|
||||
if (cmds.options.version) {
|
||||
if (commandLine.options.version) {
|
||||
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Version_0, version));
|
||||
return 0;
|
||||
sys.exit(0);
|
||||
}
|
||||
|
||||
if (cmds.filenames.length === 0 || cmds.options.help) {
|
||||
if (commandLine.options.help) {
|
||||
// TODO (drosen): Usage.
|
||||
sys.exit(0);
|
||||
}
|
||||
|
||||
if (cmds.errors.length) {
|
||||
reportDiagnostics(cmds.errors);
|
||||
return 1;
|
||||
if (commandLine.filenames.length === 0) {
|
||||
commandLine.errors.push(createCompilerDiagnostic(Diagnostics.No_input_files_specified));
|
||||
}
|
||||
|
||||
if (commandLine.errors.length) {
|
||||
reportDiagnostics(commandLine.errors);
|
||||
sys.exit(1);
|
||||
}
|
||||
|
||||
var defaultCompilerHost = createCompilerHost(commandLine.options);
|
||||
|
||||
if (commandLine.options.watch) {
|
||||
if (!sys.watchFile) {
|
||||
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch"));
|
||||
sys.exit(1);
|
||||
}
|
||||
|
||||
watchProgram(commandLine, defaultCompilerHost);
|
||||
}
|
||||
else {
|
||||
sys.exit(compile(commandLine, defaultCompilerHost).errors.length > 0 ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<FileWatcher> = {};
|
||||
var updatedFiles: Map<boolean> = {};
|
||||
|
||||
// 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 = f.filename;
|
||||
watchers[filename] = sys.watchFile(filename, fileUpdated);
|
||||
});
|
||||
}
|
||||
|
||||
function removeWatchers(program: Program) {
|
||||
forEach(program.getSourceFiles(), f => {
|
||||
var filename = 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[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<boolean>) {
|
||||
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);
|
||||
|
||||
// Gets us syntactically correct files from the last compilation.
|
||||
var getUnmodifiedSourceFile = program.getSourceFile;
|
||||
|
||||
// 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) => {
|
||||
if (!hasProperty(changedFiles, fileName)) {
|
||||
var sourceFile = getUnmodifiedSourceFile(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 compile(commandLine: ParsedCommandLine, compilerHost: CompilerHost) {
|
||||
var parseStart = new Date().getTime();
|
||||
var program = createProgram(cmds.filenames, cmds.options, createCompilerHost(cmds.options));
|
||||
var program = createProgram(commandLine.filenames, commandLine.options, compilerHost);
|
||||
var bindStart = new Date().getTime();
|
||||
var errors = program.getDiagnostics();
|
||||
if (errors.length) {
|
||||
|
@ -224,7 +324,7 @@ module ts {
|
|||
}
|
||||
|
||||
reportDiagnostics(errors);
|
||||
if (cmds.options.diagnostics) {
|
||||
if (commandLine.options.diagnostics) {
|
||||
reportCountStatistic("Files", program.getSourceFiles().length);
|
||||
reportCountStatistic("Lines", countLines(program));
|
||||
reportCountStatistic("Nodes", checker ? checker.getNodeCount() : 0);
|
||||
|
@ -237,8 +337,10 @@ module ts {
|
|||
reportTimeStatistic("Emit time", reportStart - emitStart);
|
||||
reportTimeStatistic("Total time", reportStart - parseStart);
|
||||
}
|
||||
return errors.length ? 1 : 0;
|
||||
|
||||
return { program: program, errors: errors };
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
sys.exit(ts.executeCommandLine(sys.args));
|
||||
ts.executeCommandLine(sys.args);
|
||||
|
|
|
@ -932,6 +932,7 @@ module ts {
|
|||
sourceRoot?: string;
|
||||
target?: ScriptTarget;
|
||||
version?: boolean;
|
||||
watch?: boolean;
|
||||
|
||||
[option: string]: any;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue