Merge pull request #21243 from Microsoft/watchOptions

Different watchFile and watchDirectory options through environment variable
This commit is contained in:
Sheetal Nandi
2018-03-08 12:44:53 -08:00
committed by GitHub
14 changed files with 1312 additions and 588 deletions

View File

@@ -2029,7 +2029,7 @@ namespace ts {
export function getFileNamesFromConfigSpecs(spec: ConfigFileSpecs, basePath: string, options: CompilerOptions, host: ParseConfigHost, extraFileExtensions: ReadonlyArray<JsFileExtensionInfo> = []): ExpandResult {
basePath = normalizePath(basePath);
const keyMapper = host.useCaseSensitiveFileNames ? caseSensitiveKeyMapper : caseInsensitiveKeyMapper;
const keyMapper = host.useCaseSensitiveFileNames ? identity : toLowerCase;
// Literal file names (provided via the "files" array in tsconfig.json) are stored in a
// file map with a possibly case insensitive key. We use this map later when when including
@@ -2233,24 +2233,6 @@ namespace ts {
}
}
/**
* Gets a case sensitive key.
*
* @param key The original key.
*/
function caseSensitiveKeyMapper(key: string) {
return key;
}
/**
* Gets a case insensitive key.
*
* @param key The original key.
*/
function caseInsensitiveKeyMapper(key: string) {
return key.toLowerCase();
}
/**
* Produces a cleaned version of compiler options with personally identifiying info (aka, paths) removed.
* Also converts enum values back to strings.

View File

@@ -25,6 +25,10 @@ namespace ts {
/* @internal */
namespace ts {
export const emptyArray: never[] = [] as never[];
export function closeFileWatcher(watcher: FileWatcher) {
watcher.close();
}
/** Create a MapLike with good performance. */
function createDictionaryObject<T>(): MapLike<T> {
const map = Object.create(/*prototype*/ null); // tslint:disable-line:no-null-keyword
@@ -3142,4 +3146,36 @@ namespace ts {
export function singleElementArray<T>(t: T | undefined): T[] | undefined {
return t === undefined ? undefined : [t];
}
export function enumerateInsertsAndDeletes<T, U>(newItems: ReadonlyArray<T>, oldItems: ReadonlyArray<U>, comparer: (a: T, b: U) => Comparison, inserted: (newItem: T) => void, deleted: (oldItem: U) => void, unchanged?: (oldItem: U, newItem: T) => void) {
unchanged = unchanged || noop;
let newIndex = 0;
let oldIndex = 0;
const newLen = newItems.length;
const oldLen = oldItems.length;
while (newIndex < newLen && oldIndex < oldLen) {
const newItem = newItems[newIndex];
const oldItem = oldItems[oldIndex];
const compareResult = comparer(newItem, oldItem);
if (compareResult === Comparison.LessThan) {
inserted(newItem);
newIndex++;
}
else if (compareResult === Comparison.GreaterThan) {
deleted(oldItem);
oldIndex++;
}
else {
unchanged(oldItem, newItem);
newIndex++;
oldIndex++;
}
}
while (newIndex < newLen) {
inserted(newItems[newIndex++]);
}
while (oldIndex < oldLen) {
deleted(oldItems[oldIndex++]);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -483,18 +483,17 @@ namespace ts {
}
const trace = host.trace && ((s: string) => { host.trace(s + newLine); });
const loggingEnabled = trace && (compilerOptions.diagnostics || compilerOptions.extendedDiagnostics);
const writeLog = loggingEnabled ? trace : noop;
const watchFile = compilerOptions.extendedDiagnostics ? addFileWatcherWithLogging : loggingEnabled ? addFileWatcherWithOnlyTriggerLogging : addFileWatcher;
const watchFilePath = compilerOptions.extendedDiagnostics ? addFilePathWatcherWithLogging : addFilePathWatcher;
const watchDirectoryWorker = compilerOptions.extendedDiagnostics ? addDirectoryWatcherWithLogging : addDirectoryWatcher;
const watchLogLevel = trace ? compilerOptions.extendedDiagnostics ? WatchLogLevel.Verbose :
compilerOptions.diagnostis ? WatchLogLevel.TriggerOnly : WatchLogLevel.None : WatchLogLevel.None;
const writeLog: (s: string) => void = watchLogLevel !== WatchLogLevel.None ? trace : noop;
const { watchFile, watchFilePath, watchDirectory: watchDirectoryWorker } = getWatchFactory(watchLogLevel, writeLog);
const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
let newLine = updateNewLine();
writeLog(`Current directory: ${currentDirectory} CaseSensitiveFileNames: ${useCaseSensitiveFileNames}`);
if (configFileName) {
watchFile(host, configFileName, scheduleProgramReload, writeLog);
watchFile(host, configFileName, scheduleProgramReload, PollingInterval.High);
}
const compilerHost: CompilerHost & ResolutionCacheHost = {
@@ -583,8 +582,8 @@ namespace ts {
}
// Compile the program
if (loggingEnabled) {
writeLog(`CreatingProgramWith::`);
if (watchLogLevel !== WatchLogLevel.None) {
writeLog("CreatingProgramWith::");
writeLog(` roots: ${JSON.stringify(rootFileNames)}`);
writeLog(` options: ${JSON.stringify(compilerOptions)}`);
}
@@ -677,7 +676,7 @@ namespace ts {
(hostSourceFile as FilePresentOnHost).sourceFile = sourceFile;
sourceFile.version = hostSourceFile.version.toString();
if (!(hostSourceFile as FilePresentOnHost).fileWatcher) {
(hostSourceFile as FilePresentOnHost).fileWatcher = watchFilePath(host, fileName, onSourceFileChange, path, writeLog);
(hostSourceFile as FilePresentOnHost).fileWatcher = watchFilePath(host, fileName, onSourceFileChange, PollingInterval.Low, path);
}
}
else {
@@ -691,7 +690,7 @@ namespace ts {
else {
if (sourceFile) {
sourceFile.version = initialVersion.toString();
const fileWatcher = watchFilePath(host, fileName, onSourceFileChange, path, writeLog);
const fileWatcher = watchFilePath(host, fileName, onSourceFileChange, PollingInterval.Low, path);
sourceFilesCache.set(path, { sourceFile, version: initialVersion, fileWatcher });
}
else {
@@ -854,11 +853,11 @@ namespace ts {
}
function watchDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags) {
return watchDirectoryWorker(host, directory, cb, flags, writeLog);
return watchDirectoryWorker(host, directory, cb, flags);
}
function watchMissingFilePath(missingFilePath: Path) {
return watchFilePath(host, missingFilePath, onMissingFileChange, missingFilePath, writeLog);
return watchFilePath(host, missingFilePath, onMissingFileChange, PollingInterval.Medium, missingFilePath);
}
function onMissingFileChange(fileName: string, eventKind: FileWatcherEventKind, missingFilePath: Path) {

View File

@@ -331,85 +331,101 @@ namespace ts {
return program.isEmittedFile(file);
}
export enum WatchLogLevel {
None,
TriggerOnly,
Verbose
}
export interface WatchFileHost {
watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher;
}
export function addFileWatcher(host: WatchFileHost, file: string, cb: FileWatcherCallback): FileWatcher {
return host.watchFile(file, cb);
}
export function addFileWatcherWithLogging(host: WatchFileHost, file: string, cb: FileWatcherCallback, log: (s: string) => void): FileWatcher {
const watcherCaption = `FileWatcher:: `;
return createWatcherWithLogging(addFileWatcher, watcherCaption, log, /*logOnlyTrigger*/ false, host, file, cb);
}
export function addFileWatcherWithOnlyTriggerLogging(host: WatchFileHost, file: string, cb: FileWatcherCallback, log: (s: string) => void): FileWatcher {
const watcherCaption = `FileWatcher:: `;
return createWatcherWithLogging(addFileWatcher, watcherCaption, log, /*logOnlyTrigger*/ true, host, file, cb);
}
export type FilePathWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind, filePath: Path) => void;
export function addFilePathWatcher(host: WatchFileHost, file: string, cb: FilePathWatcherCallback, path: Path): FileWatcher {
return host.watchFile(file, (fileName, eventKind) => cb(fileName, eventKind, path));
}
export function addFilePathWatcherWithLogging(host: WatchFileHost, file: string, cb: FilePathWatcherCallback, path: Path, log: (s: string) => void): FileWatcher {
const watcherCaption = `FileWatcher:: `;
return createWatcherWithLogging(addFileWatcher, watcherCaption, log, /*logOnlyTrigger*/ false, host, file, cb, path);
}
export function addFilePathWatcherWithOnlyTriggerLogging(host: WatchFileHost, file: string, cb: FilePathWatcherCallback, path: Path, log: (s: string) => void): FileWatcher {
const watcherCaption = `FileWatcher:: `;
return createWatcherWithLogging(addFileWatcher, watcherCaption, log, /*logOnlyTrigger*/ true, host, file, cb, path);
}
export interface WatchDirectoryHost {
watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
}
export type WatchFile<X, Y> = (host: WatchFileHost, file: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, detailInfo1?: X, detailInfo2?: Y) => FileWatcher;
export type FilePathWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind, filePath: Path) => void;
export type WatchFilePath<X, Y> = (host: WatchFileHost, file: string, callback: FilePathWatcherCallback, pollingInterval: PollingInterval, path: Path, detailInfo1?: X, detailInfo2?: Y) => FileWatcher;
export type WatchDirectory<X, Y> = (host: WatchDirectoryHost, directory: string, callback: DirectoryWatcherCallback, flags: WatchDirectoryFlags, detailInfo1?: X, detailInfo2?: Y) => FileWatcher;
export function addDirectoryWatcher(host: WatchDirectoryHost, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher {
const recursive = (flags & WatchDirectoryFlags.Recursive) !== 0;
return host.watchDirectory(directory, cb, recursive);
export interface WatchFactory<X, Y> {
watchFile: WatchFile<X, Y>;
watchFilePath: WatchFilePath<X, Y>;
watchDirectory: WatchDirectory<X, Y>;
}
export function addDirectoryWatcherWithLogging(host: WatchDirectoryHost, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags, log: (s: string) => void): FileWatcher {
const watcherCaption = `DirectoryWatcher ${(flags & WatchDirectoryFlags.Recursive) !== 0 ? "recursive" : ""}:: `;
return createWatcherWithLogging(addDirectoryWatcher, watcherCaption, log, /*logOnlyTrigger*/ false, host, directory, cb, flags);
export function getWatchFactory<X = undefined, Y = undefined>(watchLogLevel: WatchLogLevel, log: (s: string) => void, getDetailWatchInfo?: GetDetailWatchInfo<X, Y>): WatchFactory<X, Y> {
return getWatchFactoryWith(watchLogLevel, log, getDetailWatchInfo, watchFile, watchDirectory);
}
export function addDirectoryWatcherWithOnlyTriggerLogging(host: WatchDirectoryHost, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags, log: (s: string) => void): FileWatcher {
const watcherCaption = `DirectoryWatcher ${(flags & WatchDirectoryFlags.Recursive) !== 0 ? "recursive" : ""}:: `;
return createWatcherWithLogging(addDirectoryWatcher, watcherCaption, log, /*logOnlyTrigger*/ true, host, directory, cb, flags);
}
function getWatchFactoryWith<X = undefined, Y = undefined>(watchLogLevel: WatchLogLevel, log: (s: string) => void, getDetailWatchInfo: GetDetailWatchInfo<X, Y> | undefined,
watchFile: (host: WatchFileHost, file: string, callback: FileWatcherCallback, watchPriority: PollingInterval) => FileWatcher,
watchDirectory: (host: WatchDirectoryHost, directory: string, callback: DirectoryWatcherCallback, flags: WatchDirectoryFlags) => FileWatcher): WatchFactory<X, Y> {
const createFileWatcher: CreateFileWatcher<WatchFileHost, PollingInterval, FileWatcherEventKind, undefined, X, Y> = getCreateFileWatcher(watchLogLevel, watchFile);
const createFilePathWatcher: CreateFileWatcher<WatchFileHost, PollingInterval, FileWatcherEventKind, Path, X, Y> = watchLogLevel === WatchLogLevel.None ? watchFilePath : createFileWatcher;
const createDirectoryWatcher: CreateFileWatcher<WatchDirectoryHost, WatchDirectoryFlags, undefined, undefined, X, Y> = getCreateFileWatcher(watchLogLevel, watchDirectory);
return {
watchFile: (host, file, callback, pollingInterval, detailInfo1, detailInfo2) =>
createFileWatcher(host, file, callback, pollingInterval, /*passThrough*/ undefined, detailInfo1, detailInfo2, watchFile, log, "FileWatcher", getDetailWatchInfo),
watchFilePath: (host, file, callback, pollingInterval, path, detailInfo1, detailInfo2) =>
createFilePathWatcher(host, file, callback, pollingInterval, path, detailInfo1, detailInfo2, watchFile, log, "FileWatcher", getDetailWatchInfo),
watchDirectory: (host, directory, callback, flags, detailInfo1, detailInfo2) =>
createDirectoryWatcher(host, directory, callback, flags, /*passThrough*/ undefined, detailInfo1, detailInfo2, watchDirectory, log, "DirectoryWatcher", getDetailWatchInfo)
};
type WatchCallback<T, U> = (fileName: string, cbOptional1?: T, optional?: U) => void;
type AddWatch<H, T, U> = (host: H, file: string, cb: WatchCallback<T, U>, optional?: U) => FileWatcher;
function createWatcherWithLogging<H, T, U>(addWatch: AddWatch<H, T, U>, watcherCaption: string, log: (s: string) => void, logOnlyTrigger: boolean, host: H, file: string, cb: WatchCallback<T, U>, optional?: U): FileWatcher {
const info = `PathInfo: ${file}`;
if (!logOnlyTrigger) {
log(`${watcherCaption}Added: ${info}`);
function watchFilePath(host: WatchFileHost, file: string, callback: FilePathWatcherCallback, pollingInterval: PollingInterval, path: Path): FileWatcher {
return watchFile(host, file, (fileName, eventKind) => callback(fileName, eventKind, path), pollingInterval);
}
const watcher = addWatch(host, file, (fileName, cbOptional1?) => {
const optionalInfo = cbOptional1 !== undefined ? ` ${cbOptional1}` : "";
log(`${watcherCaption}Trigger: ${fileName}${optionalInfo} ${info}`);
const start = timestamp();
cb(fileName, cbOptional1, optional);
const elapsed = timestamp() - start;
log(`${watcherCaption}Elapsed: ${elapsed}ms Trigger: ${fileName}${optionalInfo} ${info}`);
}, optional);
}
function watchFile(host: WatchFileHost, file: string, callback: FileWatcherCallback, pollingInterval: PollingInterval): FileWatcher {
return host.watchFile(file, callback, pollingInterval);
}
function watchDirectory(host: WatchDirectoryHost, directory: string, callback: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher {
return host.watchDirectory(directory, callback, (flags & WatchDirectoryFlags.Recursive) !== 0);
}
type WatchCallback<T, U> = (fileName: string, cbOptional?: T, passThrough?: U) => void;
type AddWatch<H, T, U, V> = (host: H, file: string, cb: WatchCallback<U, V>, flags: T, passThrough?: V, detailInfo1?: undefined, detailInfo2?: undefined) => FileWatcher;
export type GetDetailWatchInfo<X, Y> = (detailInfo1: X, detailInfo2: Y) => string;
type CreateFileWatcher<H, T, U, V, X, Y> = (host: H, file: string, cb: WatchCallback<U, V>, flags: T, passThrough: V | undefined, detailInfo1: X | undefined, detailInfo2: Y | undefined, addWatch: AddWatch<H, T, U, V>, log: (s: string) => void, watchCaption: string, getDetailWatchInfo: GetDetailWatchInfo<X, Y> | undefined) => FileWatcher;
function getCreateFileWatcher<H, T, U, V, X, Y>(watchLogLevel: WatchLogLevel, addWatch: AddWatch<H, T, U, V>): CreateFileWatcher<H, T, U, V, X, Y> {
switch (watchLogLevel) {
case WatchLogLevel.None:
return addWatch;
case WatchLogLevel.TriggerOnly:
return createFileWatcherWithTriggerLogging;
case WatchLogLevel.Verbose:
return createFileWatcherWithLogging;
}
}
function createFileWatcherWithLogging<H, T, U, V, X, Y>(host: H, file: string, cb: WatchCallback<U, V>, flags: T, passThrough: V | undefined, detailInfo1: X | undefined, detailInfo2: Y | undefined, addWatch: AddWatch<H, T, U, undefined>, log: (s: string) => void, watchCaption: string, getDetailWatchInfo: GetDetailWatchInfo<X, Y> | undefined): FileWatcher {
log(`${watchCaption}:: Added:: ${getWatchInfo(file, flags, detailInfo1, detailInfo2, getDetailWatchInfo)}`);
const watcher = createFileWatcherWithTriggerLogging(host, file, cb, flags, passThrough, detailInfo1, detailInfo2, addWatch, log, watchCaption, getDetailWatchInfo);
return {
close: () => {
if (!logOnlyTrigger) {
log(`${watcherCaption}Close: ${info}`);
}
log(`${watchCaption}:: Close:: ${getWatchInfo(file, flags, detailInfo1, detailInfo2, getDetailWatchInfo)}`);
watcher.close();
}
};
}
export function closeFileWatcher(watcher: FileWatcher) {
watcher.close();
function createFileWatcherWithTriggerLogging<H, T, U, V, X, Y>(host: H, file: string, cb: WatchCallback<U, V>, flags: T, passThrough: V | undefined, detailInfo1: X | undefined, detailInfo2: Y | undefined, addWatch: AddWatch<H, T, U, undefined>, log: (s: string) => void, watchCaption: string, getDetailWatchInfo: GetDetailWatchInfo<X, Y> | undefined): FileWatcher {
return addWatch(host, file, (fileName, cbOptional) => {
const triggerredInfo = `${watchCaption}:: Triggered with ${fileName}${cbOptional !== undefined ? cbOptional : ""}:: ${getWatchInfo(file, flags, detailInfo1, detailInfo2, getDetailWatchInfo)}`;
log(triggerredInfo);
const start = timestamp();
cb(fileName, cbOptional, passThrough);
const elapsed = timestamp() - start;
log(`Elapsed:: ${elapsed}ms ${triggerredInfo}`);
}, flags);
}
function getWatchInfo<T, X, Y>(file: string, flags: T, detailInfo1: X | undefined, detailInfo2: Y | undefined, getDetailWatchInfo: GetDetailWatchInfo<X, Y> | undefined) {
return `WatchInfo: ${file} ${flags} ${getDetailWatchInfo ? getDetailWatchInfo(detailInfo1, detailInfo2) : ""}`;
}
export function closeFileWatcherOf<T extends { watcher: FileWatcher; }>(objWithWatcher: T) {

View File

@@ -3,11 +3,10 @@
/// <reference path="..\virtualFileSystemWithWatch.ts" />
namespace ts.tscWatch {
import WatchedSystem = TestFSWithWatch.TestServerHost;
type FileOrFolder = TestFSWithWatch.FileOrFolder;
import createWatchedSystem = TestFSWithWatch.createWatchedSystem;
import checkFileNames = TestFSWithWatch.checkFileNames;
import checkArray = TestFSWithWatch.checkArray;
import libFile = TestFSWithWatch.libFile;
import checkWatchedFiles = TestFSWithWatch.checkWatchedFiles;
import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories;
@@ -15,11 +14,11 @@ namespace ts.tscWatch {
import checkOutputDoesNotContain = TestFSWithWatch.checkOutputDoesNotContain;
export function checkProgramActualFiles(program: Program, expectedFiles: string[]) {
checkFileNames(`Program actual files`, program.getSourceFiles().map(file => file.fileName), expectedFiles);
checkArray(`Program actual files`, program.getSourceFiles().map(file => file.fileName), expectedFiles);
}
export function checkProgramRootFiles(program: Program, expectedFiles: string[]) {
checkFileNames(`Program rootFileNames`, program.getRootFileNames(), expectedFiles);
checkArray(`Program rootFileNames`, program.getRootFileNames(), expectedFiles);
}
function createWatchOfConfigFile(configFileName: string, host: WatchedSystem, maxNumberOfFilesToIterateForInvalidation?: number) {
@@ -73,64 +72,60 @@ namespace ts.tscWatch {
checkOutputDoesNotContain(host, expectedNonAffectedFiles);
}
enum ExpectedOutputErrorsPosition {
BeforeCompilationStarts,
AfterCompilationStarting,
AfterFileChangeDetected
}
function checkOutputErrors(
host: WatchedSystem,
preErrorsWatchDiagnostic: DiagnosticMessage | undefined,
errors: ReadonlyArray<Diagnostic>,
errorsPosition: ExpectedOutputErrorsPosition,
skipWaiting?: true
...postErrorsWatchDiagnostics: DiagnosticMessage[]
) {
const outputs = host.getOutput();
const expectedOutputCount = errors.length + (skipWaiting ? 0 : 1) + 1;
assert.equal(outputs.length, expectedOutputCount, "Outputs = " + outputs.toString());
let index: number;
switch (errorsPosition) {
case ExpectedOutputErrorsPosition.AfterCompilationStarting:
assertWatchDiagnosticAt(host, 0, Diagnostics.Starting_compilation_in_watch_mode);
index = 1;
break;
case ExpectedOutputErrorsPosition.AfterFileChangeDetected:
assertWatchDiagnosticAt(host, 0, Diagnostics.File_change_detected_Starting_incremental_compilation);
index = 1;
break;
case ExpectedOutputErrorsPosition.BeforeCompilationStarts:
assertWatchDiagnosticAt(host, errors.length, Diagnostics.Starting_compilation_in_watch_mode);
index = 0;
break;
}
forEach(errors, error => {
assertDiagnosticAt(host, index, error);
index++;
});
if (!skipWaiting) {
if (errorsPosition === ExpectedOutputErrorsPosition.BeforeCompilationStarts) {
assertWatchDiagnosticAt(host, index, Diagnostics.Starting_compilation_in_watch_mode);
index += 1;
}
assertWatchDiagnosticAt(host, index, Diagnostics.Compilation_complete_Watching_for_file_changes);
const expectedOutputCount = (preErrorsWatchDiagnostic ? 1 : 0) + errors.length + postErrorsWatchDiagnostics.length;
assert.equal(outputs.length, expectedOutputCount);
let index = 0;
if (preErrorsWatchDiagnostic) {
assertWatchDiagnostic(preErrorsWatchDiagnostic);
}
// Verify errors
forEach(errors, assertDiagnostic);
forEach(postErrorsWatchDiagnostics, assertWatchDiagnostic);
host.clearOutput();
function assertDiagnostic(diagnostic: Diagnostic) {
const expected = formatDiagnostic(diagnostic, host);
assert.equal(outputs[index], expected, getOutputAtFailedMessage("Diagnostic", expected));
index++;
}
function assertWatchDiagnostic(diagnosticMessage: DiagnosticMessage) {
const expected = getWatchDiagnosticWithoutDate(diagnosticMessage);
assert.isTrue(endsWith(outputs[index], expected), getOutputAtFailedMessage("Watch diagnostic", expected));
index++;
}
function getOutputAtFailedMessage(caption: string, expectedOutput: string) {
return `Expected ${caption}: ${expectedOutput} at ${index} in ${JSON.stringify(outputs)}`;
}
function getWatchDiagnosticWithoutDate(diagnosticMessage: DiagnosticMessage) {
return ` - ${flattenDiagnosticMessageText(getLocaleSpecificMessage(diagnosticMessage), host.newLine)}${host.newLine + host.newLine + host.newLine}`;
}
}
function assertDiagnosticAt(host: WatchedSystem, outputAt: number, diagnostic: Diagnostic) {
const output = host.getOutput()[outputAt];
assert.equal(output, formatDiagnostic(diagnostic, host), "outputs[" + outputAt + "] is " + output);
function checkOutputErrorsInitial(host: WatchedSystem, errors: ReadonlyArray<Diagnostic>) {
checkOutputErrors(host, Diagnostics.Starting_compilation_in_watch_mode, errors, Diagnostics.Compilation_complete_Watching_for_file_changes);
}
function assertWatchDiagnosticAt(host: WatchedSystem, outputAt: number, diagnosticMessage: DiagnosticMessage) {
const output = host.getOutput()[outputAt];
assert.isTrue(endsWith(output, getWatchDiagnosticWithoutDate(host, diagnosticMessage)), "outputs[" + outputAt + "] is " + output);
function checkOutputErrorsInitialWithConfigErrors(host: WatchedSystem, errors: ReadonlyArray<Diagnostic>) {
checkOutputErrors(host, /*preErrorsWatchDiagnostic*/ undefined, errors, Diagnostics.Starting_compilation_in_watch_mode, Diagnostics.Compilation_complete_Watching_for_file_changes);
}
function getWatchDiagnosticWithoutDate(host: WatchedSystem, diagnosticMessage: DiagnosticMessage) {
return ` - ${flattenDiagnosticMessageText(getLocaleSpecificMessage(diagnosticMessage), host.newLine)}${host.newLine + host.newLine + host.newLine}`;
function checkOutputErrorsIncremental(host: WatchedSystem, errors: ReadonlyArray<Diagnostic>) {
checkOutputErrors(host, Diagnostics.File_change_detected_Starting_incremental_compilation, errors, Diagnostics.Compilation_complete_Watching_for_file_changes);
}
function checkOutputErrorsIncrementalWithExit(host: WatchedSystem, errors: ReadonlyArray<Diagnostic>, expectedExitCode: ExitStatus) {
checkOutputErrors(host, Diagnostics.File_change_detected_Starting_incremental_compilation, errors);
assert.equal(host.exitCode, expectedExitCode);
}
function getDiagnosticOfFileFrom(file: SourceFile, text: string, start: number, length: number, message: DiagnosticMessage): Diagnostic {
@@ -346,16 +341,16 @@ namespace ts.tscWatch {
checkProgramRootFiles(watch(), [file1.path]);
checkProgramActualFiles(watch(), [file1.path, libFile.path]);
checkOutputErrors(host, [
checkOutputErrorsInitial(host, [
getDiagnosticOfFileFromProgram(watch(), file1.path, file1.content.indexOf(commonFile2Name), commonFile2Name.length, Diagnostics.File_0_not_found, commonFile2.path),
getDiagnosticOfFileFromProgram(watch(), file1.path, file1.content.indexOf("y"), 1, Diagnostics.Cannot_find_name_0, "y")
], /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterCompilationStarting);
]);
host.reloadFS([file1, commonFile2, libFile]);
host.runQueuedTimeoutCallbacks();
checkProgramRootFiles(watch(), [file1.path]);
checkProgramActualFiles(watch(), [file1.path, libFile.path, commonFile2.path]);
checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected);
checkOutputErrorsIncremental(host, emptyArray);
});
it("should reflect change in config file", () => {
@@ -683,15 +678,14 @@ namespace ts.tscWatch {
const watch = createWatchOfConfigFile(config.path, host);
checkProgramActualFiles(watch(), [file1.path, file2.path, libFile.path]);
checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterCompilationStarting);
checkOutputErrorsInitial(host, emptyArray);
host.reloadFS([file1, file2, libFile]);
host.checkTimeoutQueueLengthAndRun(1);
assert.equal(host.exitCode, ExitStatus.DiagnosticsPresent_OutputsSkipped);
checkOutputErrors(host, [
checkOutputErrorsIncrementalWithExit(host, [
getDiagnosticWithoutFile(Diagnostics.File_0_not_found, config.path)
], /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected, /*skipWaiting*/ true);
], ExitStatus.DiagnosticsPresent_OutputsSkipped);
});
it("Proper errors: document is not contained in project", () => {
@@ -794,21 +788,21 @@ namespace ts.tscWatch {
};
const host = createWatchedSystem([moduleFile, file1, libFile]);
const watch = createWatchOfFilesAndCompilerOptions([file1.path], host);
checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterCompilationStarting);
checkOutputErrorsInitial(host, emptyArray);
const moduleFileOldPath = moduleFile.path;
const moduleFileNewPath = "/a/b/moduleFile1.ts";
moduleFile.path = moduleFileNewPath;
host.reloadFS([moduleFile, file1, libFile]);
host.runQueuedTimeoutCallbacks();
checkOutputErrors(host, [
checkOutputErrorsIncremental(host, [
getDiagnosticModuleNotFoundOfFile(watch(), file1, "./moduleFile")
], /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected);
]);
moduleFile.path = moduleFileOldPath;
host.reloadFS([moduleFile, file1, libFile]);
host.runQueuedTimeoutCallbacks();
checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected);
checkOutputErrorsIncremental(host, emptyArray);
});
it("rename a module file and rename back should restore the states for configured projects", () => {
@@ -826,21 +820,21 @@ namespace ts.tscWatch {
};
const host = createWatchedSystem([moduleFile, file1, configFile, libFile]);
const watch = createWatchOfConfigFile(configFile.path, host);
checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterCompilationStarting);
checkOutputErrorsInitial(host, emptyArray);
const moduleFileOldPath = moduleFile.path;
const moduleFileNewPath = "/a/b/moduleFile1.ts";
moduleFile.path = moduleFileNewPath;
host.reloadFS([moduleFile, file1, configFile, libFile]);
host.runQueuedTimeoutCallbacks();
checkOutputErrors(host, [
checkOutputErrorsIncremental(host, [
getDiagnosticModuleNotFoundOfFile(watch(), file1, "./moduleFile")
], /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected);
]);
moduleFile.path = moduleFileOldPath;
host.reloadFS([moduleFile, file1, configFile, libFile]);
host.runQueuedTimeoutCallbacks();
checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected);
checkOutputErrorsIncremental(host, emptyArray);
});
it("types should load from config file path if config exists", () => {
@@ -877,13 +871,13 @@ namespace ts.tscWatch {
const host = createWatchedSystem([file1, libFile]);
const watch = createWatchOfFilesAndCompilerOptions([file1.path], host);
checkOutputErrors(host, [
checkOutputErrorsInitial(host, [
getDiagnosticModuleNotFoundOfFile(watch(), file1, "./moduleFile")
], /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterCompilationStarting);
]);
host.reloadFS([file1, moduleFile, libFile]);
host.runQueuedTimeoutCallbacks();
checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected);
checkOutputErrorsIncremental(host, emptyArray);
});
it("Configure file diagnostics events are generated when the config file has errors", () => {
@@ -903,10 +897,10 @@ namespace ts.tscWatch {
const host = createWatchedSystem([file, configFile, libFile]);
const watch = createWatchOfConfigFile(configFile.path, host);
checkOutputErrors(host, [
checkOutputErrorsInitialWithConfigErrors(host, [
getUnknownCompilerOption(watch(), configFile, "foo"),
getUnknownCompilerOption(watch(), configFile, "allowJS")
], /*errorsPosition*/ ExpectedOutputErrorsPosition.BeforeCompilationStarts);
]);
});
it("If config file doesnt have errors, they are not reported", () => {
@@ -923,7 +917,7 @@ namespace ts.tscWatch {
const host = createWatchedSystem([file, configFile, libFile]);
createWatchOfConfigFile(configFile.path, host);
checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterCompilationStarting);
checkOutputErrorsInitial(host, emptyArray);
});
it("Reports errors when the config file changes", () => {
@@ -940,7 +934,7 @@ namespace ts.tscWatch {
const host = createWatchedSystem([file, configFile, libFile]);
const watch = createWatchOfConfigFile(configFile.path, host);
checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterCompilationStarting);
checkOutputErrorsInitial(host, emptyArray);
configFile.content = `{
"compilerOptions": {
@@ -949,16 +943,16 @@ namespace ts.tscWatch {
}`;
host.reloadFS([file, configFile, libFile]);
host.runQueuedTimeoutCallbacks();
checkOutputErrors(host, [
checkOutputErrorsIncremental(host, [
getUnknownCompilerOption(watch(), configFile, "haha")
], /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected);
]);
configFile.content = `{
"compilerOptions": {}
}`;
host.reloadFS([file, configFile, libFile]);
host.runQueuedTimeoutCallbacks();
checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected);
checkOutputErrorsIncremental(host, emptyArray);
});
it("non-existing directories listed in config file input array should be tolerated without crashing the server", () => {
@@ -1046,13 +1040,13 @@ namespace ts.tscWatch {
getDiagnosticOfFile(watch().getCompilerOptions().configFile, configFile.content.indexOf('"declaration"'), '"declaration"'.length, Diagnostics.Option_0_cannot_be_specified_with_option_1, "allowJs", "declaration")
];
const intialErrors = errors();
checkOutputErrors(host, intialErrors, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterCompilationStarting);
checkOutputErrorsInitial(host, intialErrors);
configFile.content = configFileContentWithoutCommentLine;
host.reloadFS(files);
host.runQueuedTimeoutCallbacks();
const nowErrors = errors();
checkOutputErrors(host, nowErrors, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected);
checkOutputErrorsIncremental(host, nowErrors);
assert.equal(nowErrors[0].start, intialErrors[0].start - configFileContentComment.length);
assert.equal(nowErrors[1].start, intialErrors[1].start - configFileContentComment.length);
});
@@ -1105,13 +1099,13 @@ namespace ts.tscWatch {
noUnusedLocals: true
});
checkProgramActualFiles(watch(), files.map(file => file.path));
checkOutputErrors(host, [], ExpectedOutputErrorsPosition.AfterCompilationStarting);
checkOutputErrorsInitial(host, []);
file.content = getFileContent(/*asModule*/ true);
host.reloadFS(files);
host.runQueuedTimeoutCallbacks();
checkProgramActualFiles(watch(), files.map(file => file.path));
checkOutputErrors(host, [], ExpectedOutputErrorsPosition.AfterFileChangeDetected);
checkOutputErrorsIncremental(host, []);
});
it("watched files when file is deleted and new file is added as part of change", () => {
@@ -1800,7 +1794,7 @@ namespace ts.tscWatch {
const cannotFindFoo = getDiagnosticOfFileFromProgram(watch(), imported.path, imported.content.indexOf("foo"), "foo".length, Diagnostics.Cannot_find_name_0, "foo");
// ensure that imported file was found
checkOutputErrors(host, [f1IsNotModule, cannotFindFoo], /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterCompilationStarting);
checkOutputErrorsInitial(host, [f1IsNotModule, cannotFindFoo]);
const originalFileExists = host.fileExists;
{
@@ -1816,11 +1810,11 @@ namespace ts.tscWatch {
host.runQueuedTimeoutCallbacks();
// ensure file has correct number of errors after edit
checkOutputErrors(host, [
checkOutputErrorsIncremental(host, [
f1IsNotModule,
getDiagnosticOfFileFromProgram(watch(), root.path, newContent.indexOf("var x") + "var ".length, "x".length, Diagnostics.Type_0_is_not_assignable_to_type_1, 1, "string"),
cannotFindFoo
], /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected);
]);
}
{
let fileExistsIsCalled = false;
@@ -1840,9 +1834,9 @@ namespace ts.tscWatch {
host.runQueuedTimeoutCallbacks();
// ensure file has correct number of errors after edit
checkOutputErrors(host, [
checkOutputErrorsIncremental(host, [
getDiagnosticModuleNotFoundOfFile(watch(), root, "f2")
], /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected);
]);
assert.isTrue(fileExistsIsCalled);
}
@@ -1863,7 +1857,7 @@ namespace ts.tscWatch {
host.reloadFS(files);
host.runQueuedTimeoutCallbacks();
checkOutputErrors(host, [f1IsNotModule, cannotFindFoo], /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected);
checkOutputErrorsIncremental(host, [f1IsNotModule, cannotFindFoo]);
assert.isTrue(fileExistsCalled);
}
});
@@ -1898,16 +1892,16 @@ namespace ts.tscWatch {
const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD });
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called");
checkOutputErrors(host, [
checkOutputErrorsInitial(host, [
getDiagnosticModuleNotFoundOfFile(watch(), root, "bar")
], /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterCompilationStarting);
]);
fileExistsCalledForBar = false;
root.content = `import {y} from "bar"`;
host.reloadFS(files.concat(imported));
host.runQueuedTimeoutCallbacks();
checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected);
checkOutputErrorsIncremental(host, emptyArray);
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called.");
});
@@ -1940,20 +1934,20 @@ namespace ts.tscWatch {
const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD });
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called");
checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterCompilationStarting);
checkOutputErrorsInitial(host, emptyArray);
fileExistsCalledForBar = false;
host.reloadFS(files);
host.runQueuedTimeoutCallbacks();
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called.");
checkOutputErrors(host, [
checkOutputErrorsIncremental(host, [
getDiagnosticModuleNotFoundOfFile(watch(), root, "bar")
], /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected);
]);
fileExistsCalledForBar = false;
host.reloadFS(filesWithImported);
host.checkTimeoutQueueLengthAndRun(1);
checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected);
checkOutputErrorsIncremental(host, emptyArray);
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called.");
});
@@ -1988,13 +1982,13 @@ declare module "fs" {
const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { });
checkOutputErrors(host, [
checkOutputErrorsInitial(host, [
getDiagnosticModuleNotFoundOfFile(watch(), root, "fs")
], /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterCompilationStarting);
]);
host.reloadFS(filesWithNodeType);
host.runQueuedTimeoutCallbacks();
checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected);
checkOutputErrorsIncremental(host, emptyArray);
});
it("works when included file with ambient module changes", () => {
@@ -2030,14 +2024,14 @@ declare module "fs" {
const watch = createWatchOfFilesAndCompilerOptions([root.path, file.path], host, {});
checkOutputErrors(host, [
checkOutputErrorsInitial(host, [
getDiagnosticModuleNotFoundOfFile(watch(), root, "fs")
], /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterCompilationStarting);
]);
file.content += fileContentWithFS;
host.reloadFS(files);
host.runQueuedTimeoutCallbacks();
checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected);
checkOutputErrorsIncremental(host, emptyArray);
});
it("works when reusing program with files from external library", () => {
@@ -2072,7 +2066,7 @@ declare module "fs" {
const host = createWatchedSystem(programFiles.concat(configFile), { currentDirectory: "/a/b/projects/myProject/" });
const watch = createWatchOfConfigFile(configFile.path, host);
checkProgramActualFiles(watch(), programFiles.map(f => f.path));
checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterCompilationStarting);
checkOutputErrorsInitial(host, emptyArray);
const expectedFiles: ExpectedFile[] = [
createExpectedEmittedFile(file1),
createExpectedEmittedFile(file2),
@@ -2091,7 +2085,7 @@ declare module "fs" {
host.reloadFS(programFiles.concat(configFile));
host.runQueuedTimeoutCallbacks();
checkProgramActualFiles(watch(), programFiles.map(f => f.path));
checkOutputErrors(host, emptyArray, /*errorsPosition*/ ExpectedOutputErrorsPosition.AfterFileChangeDetected);
checkOutputErrorsIncremental(host, emptyArray);
verifyExpectedFiles(expectedFiles);
@@ -2212,4 +2206,136 @@ declare module "fs" {
});
});
});
describe("tsc-watch with different polling/non polling options", () => {
it("watchFile using dynamic priority polling", () => {
const projectFolder = "/a/username/project";
const file1: FileOrFolder = {
path: `${projectFolder}/typescript.ts`,
content: "var z = 10;"
};
const files = [file1, libFile];
const environmentVariables = createMap<string>();
environmentVariables.set("TSC_WATCHFILE", "DynamicPriorityPolling");
const host = createWatchedSystem(files, { environmentVariables });
const watch = createWatchOfFilesAndCompilerOptions([file1.path], host);
const initialProgram = watch();
verifyProgram();
const mediumPollingIntervalThreshold = unchangedPollThresholds[PollingInterval.Medium];
for (let index = 0; index < mediumPollingIntervalThreshold; index++) {
// Transition libFile and file1 to low priority queue
host.checkTimeoutQueueLengthAndRun(1);
assert.deepEqual(watch(), initialProgram);
}
// Make a change to file
file1.content = "var zz30 = 100;";
host.reloadFS(files);
// This should detect change in the file
host.checkTimeoutQueueLengthAndRun(1);
assert.deepEqual(watch(), initialProgram);
// Callbacks: medium priority + high priority queue and scheduled program update
host.checkTimeoutQueueLengthAndRun(3);
// During this timeout the file would be detected as unchanged
let fileUnchangeDetected = 1;
const newProgram = watch();
assert.notStrictEqual(newProgram, initialProgram);
verifyProgram();
const outputFile1 = changeExtension(file1.path, ".js");
assert.isTrue(host.fileExists(outputFile1));
assert.equal(host.readFile(outputFile1), file1.content + host.newLine);
const newThreshold = unchangedPollThresholds[PollingInterval.Low] + mediumPollingIntervalThreshold;
for (; fileUnchangeDetected < newThreshold; fileUnchangeDetected++) {
// For high + Medium/low polling interval
host.checkTimeoutQueueLengthAndRun(2);
assert.deepEqual(watch(), newProgram);
}
// Everything goes in high polling interval queue
host.checkTimeoutQueueLengthAndRun(1);
assert.deepEqual(watch(), newProgram);
function verifyProgram() {
checkProgramActualFiles(watch(), files.map(f => f.path));
checkWatchedFiles(host, []);
checkWatchedDirectories(host, [], /*recursive*/ false);
checkWatchedDirectories(host, [], /*recursive*/ true);
}
});
describe("tsc-watch when watchDirectories implementation", () => {
function verifyRenamingFileInSubFolder(tscWatchDirectory: TestFSWithWatch.Tsc_WatchDirectory) {
const projectFolder = "/a/username/project";
const projectSrcFolder = `${projectFolder}/src`;
const configFile: FileOrFolder = {
path: `${projectFolder}/tsconfig.json`,
content: "{}"
};
const file: FileOrFolder = {
path: `${projectSrcFolder}/file1.ts`,
content: ""
};
const programFiles = [file, libFile];
const files = [file, configFile, libFile];
const environmentVariables = createMap<string>();
environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory);
const host = createWatchedSystem(files, { environmentVariables });
const watch = createWatchOfConfigFile(configFile.path, host);
const projectFolders = [projectFolder, projectSrcFolder, `${projectFolder}/node_modules/@types`];
// Watching files config file, file, lib file
const expectedWatchedFiles = files.map(f => f.path);
const expectedWatchedDirectories = tscWatchDirectory === TestFSWithWatch.Tsc_WatchDirectory.NonRecursiveWatchDirectory ? projectFolders : emptyArray;
if (tscWatchDirectory === TestFSWithWatch.Tsc_WatchDirectory.WatchFile) {
expectedWatchedFiles.push(...projectFolders);
}
verifyProgram(checkOutputErrorsInitial);
// Rename the file:
file.path = file.path.replace("file1.ts", "file2.ts");
expectedWatchedFiles[0] = file.path;
host.reloadFS(files);
if (tscWatchDirectory === TestFSWithWatch.Tsc_WatchDirectory.DynamicPolling) {
// With dynamic polling the fs change would be detected only by running timeouts
host.runQueuedTimeoutCallbacks();
}
// Delayed update program
host.runQueuedTimeoutCallbacks();
verifyProgram(checkOutputErrorsIncremental);
function verifyProgram(checkOutputErrors: (host: WatchedSystem, errors: ReadonlyArray<Diagnostic>) => void) {
checkProgramActualFiles(watch(), programFiles.map(f => f.path));
checkOutputErrors(host, emptyArray);
const outputFile = changeExtension(file.path, ".js");
assert(host.fileExists(outputFile));
assert.equal(host.readFile(outputFile), file.content);
checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
// Watching config file, file, lib file and directories
TestFSWithWatch.checkMultiMapEachKeyWithCount("watchedFiles", host.watchedFiles, expectedWatchedFiles, 1);
TestFSWithWatch.checkMultiMapEachKeyWithCount("watchedDirectories", host.watchedDirectories, expectedWatchedDirectories, 1);
}
}
it("uses watchFile when renaming file in subfolder", () => {
verifyRenamingFileInSubFolder(TestFSWithWatch.Tsc_WatchDirectory.WatchFile);
});
it("uses non recursive watchDirectory when renaming file in subfolder", () => {
verifyRenamingFileInSubFolder(TestFSWithWatch.Tsc_WatchDirectory.NonRecursiveWatchDirectory);
});
it("uses non recursive dynamic polling when renaming file in subfolder", () => {
verifyRenamingFileInSubFolder(TestFSWithWatch.Tsc_WatchDirectory.DynamicPolling);
});
});
});
}

View File

@@ -10,7 +10,7 @@ namespace ts.projectSystem {
export import TestServerHost = TestFSWithWatch.TestServerHost;
export type FileOrFolder = TestFSWithWatch.FileOrFolder;
export import createServerHost = TestFSWithWatch.createServerHost;
export import checkFileNames = TestFSWithWatch.checkFileNames;
export import checkArray = TestFSWithWatch.checkArray;
export import libFile = TestFSWithWatch.libFile;
export import checkWatchedFiles = TestFSWithWatch.checkWatchedFiles;
import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories;
@@ -355,11 +355,11 @@ namespace ts.projectSystem {
}
export function checkProjectActualFiles(project: server.Project, expectedFiles: string[]) {
checkFileNames(`${server.ProjectKind[project.projectKind]} project, actual files`, project.getFileNames(), expectedFiles);
checkArray(`${server.ProjectKind[project.projectKind]} project, actual files`, project.getFileNames(), expectedFiles);
}
function checkProjectRootFiles(project: server.Project, expectedFiles: string[]) {
checkFileNames(`${server.ProjectKind[project.projectKind]} project, rootFileNames`, project.getRootFiles(), expectedFiles);
checkArray(`${server.ProjectKind[project.projectKind]} project, rootFileNames`, project.getRootFiles(), expectedFiles);
}
function mapCombinedPathsInAncestor(dir: string, path2: string, mapAncestor: (ancestor: string) => boolean) {
@@ -392,7 +392,7 @@ namespace ts.projectSystem {
}
function checkOpenFiles(projectService: server.ProjectService, expectedFiles: FileOrFolder[]) {
checkFileNames("Open files", arrayFrom(projectService.openFiles.keys(), path => projectService.getScriptInfoForPath(path as Path).fileName), expectedFiles.map(file => file.path));
checkArray("Open files", arrayFrom(projectService.openFiles.keys(), path => projectService.getScriptInfoForPath(path as Path).fileName), expectedFiles.map(file => file.path));
}
/**
@@ -531,7 +531,7 @@ namespace ts.projectSystem {
const project = projectService.inferredProjects[0];
checkFileNames("inferred project", project.getFileNames(), [appFile.path, libFile.path, moduleFile.path]);
checkArray("inferred project", project.getFileNames(), [appFile.path, libFile.path, moduleFile.path]);
const configFileLocations = ["/a/b/c/", "/a/b/", "/a/", "/"];
const configFiles = flatMap(configFileLocations, location => [location + "tsconfig.json", location + "jsconfig.json"]);
checkWatchedFiles(host, configFiles.concat(libFile.path, moduleFile.path));
@@ -5663,16 +5663,11 @@ namespace ts.projectSystem {
}
function verifyCalledOnEachEntry(callback: CalledMaps, expectedKeys: Map<number>) {
const calledMap = calledMaps[callback];
TestFSWithWatch.verifyMapSize(callback, calledMap, arrayFrom(expectedKeys.keys()));
expectedKeys.forEach((called, name) => {
assert.isTrue(calledMap.has(name), `${callback} is expected to contain ${name}, actual keys: ${arrayFrom(calledMap.keys())}`);
assert.equal(calledMap.get(name).length, called, `${callback} is expected to be called ${called} times with ${name}. Actual entry: ${calledMap.get(name)}`);
});
TestFSWithWatch.checkMultiMapKeyCount(callback, calledMaps[callback], expectedKeys);
}
function verifyCalledOnEachEntryNTimes(callback: CalledMaps, expectedKeys: string[], nTimes: number) {
return verifyCalledOnEachEntry(callback, zipToMap(expectedKeys, expectedKeys.map(() => nTimes)));
TestFSWithWatch.checkMultiMapEachKeyWithCount(callback, calledMaps[callback], expectedKeys, nTimes);
}
function verifyNoHostCalls() {
@@ -7009,7 +7004,6 @@ namespace ts.projectSystem {
assert.isDefined(projectService.configuredProjects.get(aTsconfig.path));
assert.isDefined(projectService.configuredProjects.get(bTsconfig.path));
debugger;
verifyRenameResponse(session.executeCommandSeq<protocol.RenameRequest>({
command: protocol.CommandTypes.Rename,
arguments: {
@@ -7438,4 +7432,88 @@ namespace ts.projectSystem {
});
});
});
describe("watchDirectories implementation", () => {
function verifyCompletionListWithNewFileInSubFolder(tscWatchDirectory: TestFSWithWatch.Tsc_WatchDirectory) {
const projectFolder = "/a/username/project";
const projectSrcFolder = `${projectFolder}/src`;
const configFile: FileOrFolder = {
path: `${projectFolder}/tsconfig.json`,
content: "{}"
};
const index: FileOrFolder = {
path: `${projectSrcFolder}/index.ts`,
content: `import {} from "./"`
};
const file1: FileOrFolder = {
path: `${projectSrcFolder}/file1.ts`,
content: ""
};
const files = [index, file1, configFile, libFile];
const fileNames = files.map(file => file.path);
// All closed files(files other than index), project folder, project/src folder and project/node_modules/@types folder
const expectedWatchedFiles = arrayToMap(fileNames.slice(1), s => s, () => 1);
const expectedWatchedDirectories = createMap<number>();
const mapOfDirectories = tscWatchDirectory === TestFSWithWatch.Tsc_WatchDirectory.NonRecursiveWatchDirectory ?
expectedWatchedDirectories :
tscWatchDirectory === TestFSWithWatch.Tsc_WatchDirectory.WatchFile ?
expectedWatchedFiles :
createMap();
// For failed resolution lookup and tsconfig files
mapOfDirectories.set(projectFolder, 2);
// Through above recursive watches
mapOfDirectories.set(projectSrcFolder, 2);
// node_modules/@types folder
mapOfDirectories.set(`${projectFolder}/${nodeModulesAtTypes}`, 1);
const expectedCompletions = ["file1"];
const completionPosition = index.content.lastIndexOf('"');
const environmentVariables = createMap<string>();
environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory);
const host = createServerHost(files, { environmentVariables });
const projectService = createProjectService(host);
projectService.openClientFile(index.path);
const project = projectService.configuredProjects.get(configFile.path);
assert.isDefined(project);
verifyProjectAndCompletions();
// Add file2
const file2: FileOrFolder = {
path: `${projectSrcFolder}/file2.ts`,
content: ""
};
files.push(file2);
fileNames.push(file2.path);
expectedWatchedFiles.set(file2.path, 1);
expectedCompletions.push("file2");
host.reloadFS(files);
host.runQueuedTimeoutCallbacks();
assert.equal(projectService.configuredProjects.get(configFile.path), project);
verifyProjectAndCompletions();
function verifyProjectAndCompletions() {
const completions = project.getLanguageService().getCompletionsAtPosition(index.path, completionPosition, { includeExternalModuleExports: false, includeInsertTextCompletions: false });
checkArray("Completion Entries", completions.entries.map(e => e.name), expectedCompletions);
checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
TestFSWithWatch.checkMultiMapKeyCount("watchedFiles", host.watchedFiles, expectedWatchedFiles);
TestFSWithWatch.checkMultiMapKeyCount("watchedDirectories", host.watchedDirectories, expectedWatchedDirectories);
checkProjectActualFiles(project, fileNames);
}
}
it("uses watchFile when file is added to subfolder, completion list has new file", () => {
verifyCompletionListWithNewFileInSubFolder(TestFSWithWatch.Tsc_WatchDirectory.WatchFile);
});
it("uses non recursive watchDirectory when file is added to subfolder, completion list has new file", () => {
verifyCompletionListWithNewFileInSubFolder(TestFSWithWatch.Tsc_WatchDirectory.NonRecursiveWatchDirectory);
});
it("uses dynamic polling when file is added to subfolder, completion list has new file", () => {
verifyCompletionListWithNewFileInSubFolder(TestFSWithWatch.Tsc_WatchDirectory.DynamicPolling);
});
});
}

View File

@@ -36,6 +36,7 @@ interface Array<T> {}`
currentDirectory?: string;
newLine?: string;
useWindowsStylePaths?: boolean;
environmentVariables?: Map<string>;
}
export function createWatchedSystem(fileOrFolderList: ReadonlyArray<FileOrFolder>, params?: TestServerHostCreationParameters): TestServerHost {
@@ -48,7 +49,8 @@ interface Array<T> {}`
params.currentDirectory || "/",
fileOrFolderList,
params.newLine,
params.useWindowsStylePaths);
params.useWindowsStylePaths,
params.environmentVariables);
return host;
}
@@ -62,7 +64,8 @@ interface Array<T> {}`
params.currentDirectory || "/",
fileOrFolderList,
params.newLine,
params.useWindowsStylePaths);
params.useWindowsStylePaths,
params.environmentVariables);
return host;
}
@@ -76,6 +79,7 @@ interface Array<T> {}`
interface FSEntry {
path: Path;
fullPath: string;
modifiedTime: Date;
}
interface File extends FSEntry {
@@ -152,10 +156,22 @@ interface Array<T> {}`
}
}
export function checkFileNames(caption: string, actualFileNames: ReadonlyArray<string>, expectedFileNames: string[]) {
assert.equal(actualFileNames.length, expectedFileNames.length, `${caption}: incorrect actual number of files, expected:\r\n${expectedFileNames.join("\r\n")}\r\ngot: ${actualFileNames.join("\r\n")}`);
for (const f of expectedFileNames) {
assert.equal(true, contains(actualFileNames, f), `${caption}: expected to find ${f} in ${actualFileNames}`);
export function checkMultiMapKeyCount(caption: string, actual: MultiMap<any>, expectedKeys: Map<number>) {
verifyMapSize(caption, actual, arrayFrom(expectedKeys.keys()));
expectedKeys.forEach((count, name) => {
assert.isTrue(actual.has(name), `${caption}: expected to contain ${name}, actual keys: ${arrayFrom(actual.keys())}`);
assert.equal(actual.get(name).length, count, `${caption}: Expected to be have ${count} entries for ${name}. Actual entry: ${JSON.stringify(actual.get(name))}`);
});
}
export function checkMultiMapEachKeyWithCount(caption: string, actual: MultiMap<any>, expectedKeys: ReadonlyArray<string>, count: number) {
return checkMultiMapKeyCount(caption, actual, arrayToMap(expectedKeys, s => s, () => count));
}
export function checkArray(caption: string, actual: ReadonlyArray<string>, expected: ReadonlyArray<string>) {
assert.equal(actual.length, expected.length, `${caption}: incorrect actual number of files, expected:\r\n${expected.join("\r\n")}\r\ngot: ${actual.join("\r\n")}`);
for (const f of expected) {
assert.equal(true, contains(actual, f), `${caption}: expected to find ${f} in ${actual}`);
}
}
@@ -254,6 +270,12 @@ interface Array<T> {}`
invokeFileDeleteCreateAsPartInsteadOfChange: boolean;
}
export enum Tsc_WatchDirectory {
WatchFile = "RecursiveDirectoryUsingFsWatchFile",
NonRecursiveWatchDirectory = "RecursiveDirectoryUsingNonRecursiveWatchDirectory",
DynamicPolling = "RecursiveDirectoryUsingDynamicPriorityPolling"
}
export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost, ModuleResolutionHost {
args: string[] = [];
@@ -271,13 +293,47 @@ interface Array<T> {}`
readonly watchedFiles = createMultiMap<TestFileWatcher>();
private readonly executingFilePath: string;
private readonly currentDirectory: string;
private readonly dynamicPriorityWatchFile: HostWatchFile;
private readonly customRecursiveWatchDirectory: HostWatchDirectory | undefined;
constructor(public withSafeList: boolean, public useCaseSensitiveFileNames: boolean, executingFilePath: string, currentDirectory: string, fileOrFolderList: ReadonlyArray<FileOrFolder>, public readonly newLine = "\n", public readonly useWindowsStylePath?: boolean) {
constructor(public withSafeList: boolean, public useCaseSensitiveFileNames: boolean, executingFilePath: string, currentDirectory: string, fileOrFolderList: ReadonlyArray<FileOrFolder>, public readonly newLine = "\n", public readonly useWindowsStylePath?: boolean, private readonly environmentVariables?: Map<string>) {
this.getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
this.toPath = s => toPath(s, currentDirectory, this.getCanonicalFileName);
this.executingFilePath = this.getHostSpecificPath(executingFilePath);
this.currentDirectory = this.getHostSpecificPath(currentDirectory);
this.reloadFS(fileOrFolderList);
this.dynamicPriorityWatchFile = this.environmentVariables && this.environmentVariables.get("TSC_WATCHFILE") === "DynamicPriorityPolling" ?
createDynamicPriorityPollingWatchFile(this) :
undefined;
const tscWatchDirectory = this.environmentVariables && this.environmentVariables.get("TSC_WATCHDIRECTORY") as Tsc_WatchDirectory;
if (tscWatchDirectory === Tsc_WatchDirectory.WatchFile) {
const watchDirectory: HostWatchDirectory = (directory, cb) => this.watchFile(directory, () => cb(directory), PollingInterval.Medium);
this.customRecursiveWatchDirectory = createRecursiveDirectoryWatcher({
directoryExists: path => this.directoryExists(path),
getAccessileSortedChildDirectories: path => this.getDirectories(path),
filePathComparer: this.useCaseSensitiveFileNames ? compareStringsCaseSensitive : compareStringsCaseInsensitive,
watchDirectory
});
}
else if (tscWatchDirectory === Tsc_WatchDirectory.NonRecursiveWatchDirectory) {
const watchDirectory: HostWatchDirectory = (directory, cb) => this.watchDirectory(directory, fileName => cb(fileName), /*recursive*/ false);
this.customRecursiveWatchDirectory = createRecursiveDirectoryWatcher({
directoryExists: path => this.directoryExists(path),
getAccessileSortedChildDirectories: path => this.getDirectories(path),
filePathComparer: this.useCaseSensitiveFileNames ? compareStringsCaseSensitive : compareStringsCaseInsensitive,
watchDirectory
});
}
else if (tscWatchDirectory === Tsc_WatchDirectory.DynamicPolling) {
const watchFile = createDynamicPriorityPollingWatchFile(this);
const watchDirectory: HostWatchDirectory = (directory, cb) => watchFile(directory, () => cb(directory), PollingInterval.Medium);
this.customRecursiveWatchDirectory = createRecursiveDirectoryWatcher({
directoryExists: path => this.directoryExists(path),
getAccessileSortedChildDirectories: path => this.getDirectories(path),
filePathComparer: this.useCaseSensitiveFileNames ? compareStringsCaseSensitive : compareStringsCaseInsensitive,
watchDirectory
});
}
}
getNewLine() {
@@ -325,6 +381,8 @@ interface Array<T> {}`
}
else {
currentEntry.content = fileOrDirectory.content;
currentEntry.modifiedTime = new Date();
this.fs.get(getDirectoryPath(currentEntry.path)).modifiedTime = new Date();
if (options && options.invokeDirectoryWatcherInsteadOfFileChanged) {
this.invokeDirectoryWatcher(getDirectoryPath(currentEntry.fullPath), currentEntry.fullPath);
}
@@ -348,6 +406,7 @@ interface Array<T> {}`
}
else {
// Folder update: Nothing to do.
currentEntry.modifiedTime = new Date();
}
}
}
@@ -446,14 +505,13 @@ interface Array<T> {}`
private addFileOrFolderInFolder(folder: Folder, fileOrDirectory: File | Folder | SymLink, ignoreWatch?: boolean) {
folder.entries.push(fileOrDirectory);
folder.modifiedTime = new Date();
this.fs.set(fileOrDirectory.path, fileOrDirectory);
if (ignoreWatch) {
return;
}
if (isFile(fileOrDirectory) || isSymLink(fileOrDirectory)) {
this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Created);
}
this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Created);
this.invokeDirectoryWatcher(folder.fullPath, fileOrDirectory.fullPath);
}
@@ -462,14 +520,13 @@ interface Array<T> {}`
const baseFolder = this.fs.get(basePath) as Folder;
if (basePath !== fileOrDirectory.path) {
Debug.assert(!!baseFolder);
baseFolder.modifiedTime = new Date();
filterMutate(baseFolder.entries, entry => entry !== fileOrDirectory);
}
this.fs.delete(fileOrDirectory.path);
if (isFile(fileOrDirectory) || isSymLink(fileOrDirectory)) {
this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Deleted);
}
else {
this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Deleted);
if (isFolder(fileOrDirectory)) {
Debug.assert(fileOrDirectory.entries.length === 0 || isRenaming);
const relativePath = this.getRelativePathToDirectory(fileOrDirectory.fullPath, fileOrDirectory.fullPath);
// Invoke directory and recursive directory watcher for the folder
@@ -503,6 +560,8 @@ interface Array<T> {}`
*/
private invokeDirectoryWatcher(folderFullPath: string, fileName: string) {
const relativePath = this.getRelativePathToDirectory(folderFullPath, fileName);
// Folder is changed when the directory watcher is invoked
invokeWatcherCallbacks(this.watchedFiles.get(this.toPath(folderFullPath)), ({ cb, fileName }) => cb(fileName, FileWatcherEventKind.Changed));
invokeWatcherCallbacks(this.watchedDirectories.get(this.toPath(folderFullPath)), cb => this.directoryCallback(cb, relativePath));
this.invokeRecursiveDirectoryWatcher(folderFullPath, fileName);
}
@@ -523,34 +582,34 @@ interface Array<T> {}`
}
}
private toFile(fileOrDirectory: FileOrFolder): File {
const fullPath = getNormalizedAbsolutePath(fileOrDirectory.path, this.currentDirectory);
return {
path: this.toPath(fullPath),
content: fileOrDirectory.content,
fullPath,
fileSize: fileOrDirectory.fileSize
};
}
private toSymLink(fileOrDirectory: FileOrFolder): SymLink {
const fullPath = getNormalizedAbsolutePath(fileOrDirectory.path, this.currentDirectory);
return {
path: this.toPath(fullPath),
fullPath,
symLink: getNormalizedAbsolutePath(fileOrDirectory.symLink, getDirectoryPath(fullPath))
};
}
private toFolder(path: string): Folder {
private toFsEntry(path: string): FSEntry {
const fullPath = getNormalizedAbsolutePath(path, this.currentDirectory);
return {
path: this.toPath(fullPath),
entries: [],
fullPath
fullPath,
modifiedTime: new Date()
};
}
private toFile(fileOrDirectory: FileOrFolder): File {
const file = this.toFsEntry(fileOrDirectory.path) as File;
file.content = fileOrDirectory.content;
file.fileSize = fileOrDirectory.fileSize;
return file;
}
private toSymLink(fileOrDirectory: FileOrFolder): SymLink {
const symLink = this.toFsEntry(fileOrDirectory.path) as SymLink;
symLink.symLink = getNormalizedAbsolutePath(fileOrDirectory.symLink, getDirectoryPath(symLink.fullPath));
return symLink;
}
private toFolder(path: string): Folder {
const folder = this.toFsEntry(path) as Folder;
folder.entries = [];
return folder;
}
private getRealFsEntry<T extends FSEntry>(isFsEntry: (fsEntry: FSEntry) => fsEntry is T, path: Path, fsEntry = this.fs.get(path)): T | undefined {
if (isFsEntry(fsEntry)) {
return fsEntry;
@@ -594,6 +653,12 @@ interface Array<T> {}`
return !!this.getRealFile(path);
}
getModifiedTime(s: string) {
const path = this.toFullPath(s);
const fsEntry = this.fs.get(path);
return fsEntry && fsEntry.modifiedTime;
}
readFile(s: string): string {
const fsEntry = this.getRealFile(this.toFullPath(s));
return fsEntry ? fsEntry.content : undefined;
@@ -646,6 +711,9 @@ interface Array<T> {}`
}
watchDirectory(directoryName: string, cb: DirectoryWatcherCallback, recursive: boolean): FileWatcher {
if (recursive && this.customRecursiveWatchDirectory) {
return this.customRecursiveWatchDirectory(directoryName, cb, /*recursive*/ true);
}
const path = this.toFullPath(directoryName);
const map = recursive ? this.watchedDirectoriesRecursive : this.watchedDirectories;
const callback: TestDirectoryWatcher = {
@@ -662,7 +730,11 @@ interface Array<T> {}`
return Harness.mockHash(s);
}
watchFile(fileName: string, cb: FileWatcherCallback) {
watchFile(fileName: string, cb: FileWatcherCallback, pollingInterval: number) {
if (this.dynamicPriorityWatchFile) {
return this.dynamicPriorityWatchFile(fileName, cb, pollingInterval);
}
const path = this.toFullPath(fileName);
const callback: TestFileWatcher = { fileName, cb };
this.watchedFiles.add(path, callback);
@@ -701,7 +773,7 @@ interface Array<T> {}`
this.timeoutCallbacks.invoke(timeoutId);
}
catch (e) {
if (e.message === this.existMessage) {
if (e.message === this.exitMessage) {
return;
}
throw e;
@@ -779,15 +851,17 @@ interface Array<T> {}`
return realFullPath;
}
readonly existMessage = "System Exit";
readonly exitMessage = "System Exit";
exitCode: number;
readonly resolvePath = (s: string) => s;
readonly getExecutingFilePath = () => this.executingFilePath;
readonly getCurrentDirectory = () => this.currentDirectory;
exit(exitCode?: number) {
this.exitCode = exitCode;
throw new Error(this.existMessage);
throw new Error(this.exitMessage);
}
getEnvironmentVariable(name: string) {
return this.environmentVariables && this.environmentVariables.get(name);
}
readonly getEnvironmentVariable = notImplemented;
}
}

View File

@@ -311,9 +311,9 @@ namespace ts.server {
typesMapLocation?: string;
}
type WatchFile = (host: ServerHost, file: string, cb: FileWatcherCallback, watchType: WatchType, project?: Project) => FileWatcher;
type WatchFilePath = (host: ServerHost, file: string, cb: FilePathWatcherCallback, path: Path, watchType: WatchType, project?: Project) => FileWatcher;
type WatchDirectory = (host: ServerHost, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags, watchType: WatchType, project?: Project) => FileWatcher;
function getDetailWatchInfo(watchType: WatchType, project: Project | undefined) {
return `Project: ${project ? project.getProjectName() : ""} WatchType: ${watchType}`;
}
export class ProjectService {
@@ -401,11 +401,7 @@ namespace ts.server {
private readonly seenProjects = createMap<true>();
/*@internal*/
readonly watchFile: WatchFile;
/*@internal*/
readonly watchFilePath: WatchFilePath;
/*@internal*/
readonly watchDirectory: WatchDirectory;
readonly watchFactory: WatchFactory<WatchType, Project>;
constructor(opts: ProjectServiceOptions) {
this.host = opts.host;
@@ -447,26 +443,10 @@ namespace ts.server {
};
this.documentRegistry = createDocumentRegistry(this.host.useCaseSensitiveFileNames, this.currentDirectory);
if (this.logger.hasLevel(LogLevel.verbose)) {
this.watchFile = (host, file, cb, watchType, project) => addFileWatcherWithLogging(host, file, cb, this.createWatcherLog(watchType, project));
this.watchFilePath = (host, file, cb, path, watchType, project) => addFilePathWatcherWithLogging(host, file, cb, path, this.createWatcherLog(watchType, project));
this.watchDirectory = (host, dir, cb, flags, watchType, project) => addDirectoryWatcherWithLogging(host, dir, cb, flags, this.createWatcherLog(watchType, project));
}
else if (this.logger.loggingEnabled()) {
this.watchFile = (host, file, cb, watchType, project) => addFileWatcherWithOnlyTriggerLogging(host, file, cb, this.createWatcherLog(watchType, project));
this.watchFilePath = (host, file, cb, path, watchType, project) => addFilePathWatcherWithOnlyTriggerLogging(host, file, cb, path, this.createWatcherLog(watchType, project));
this.watchDirectory = (host, dir, cb, flags, watchType, project) => addDirectoryWatcherWithOnlyTriggerLogging(host, dir, cb, flags, this.createWatcherLog(watchType, project));
}
else {
this.watchFile = addFileWatcher;
this.watchFilePath = addFilePathWatcher;
this.watchDirectory = addDirectoryWatcher;
}
}
private createWatcherLog(watchType: WatchType, project: Project | undefined): (s: string) => void {
const detailedInfo = ` Project: ${project ? project.getProjectName() : ""} WatchType: ${watchType}`;
return s => this.logger.info(s + detailedInfo);
const watchLogLevel = this.logger.hasLevel(LogLevel.verbose) ? WatchLogLevel.Verbose :
this.logger.loggingEnabled() ? WatchLogLevel.TriggerOnly : WatchLogLevel.None;
const log: (s: string) => void = watchLogLevel !== WatchLogLevel.None ? (s => this.logger.info(s)) : noop;
this.watchFactory = getWatchFactory(watchLogLevel, log, getDetailWatchInfo);
}
toPath(fileName: string) {
@@ -714,8 +694,8 @@ namespace ts.server {
return formatCodeSettings || this.hostConfiguration.formatCodeOptions;
}
private onSourceFileChanged(fileName: NormalizedPath, eventKind: FileWatcherEventKind) {
const info = this.getScriptInfoForNormalizedPath(fileName);
private onSourceFileChanged(fileName: string, eventKind: FileWatcherEventKind, path: Path) {
const info = this.getScriptInfoForPath(path);
if (!info) {
this.logger.msg(`Error: got watch notification for unknown file: ${fileName}`);
}
@@ -759,7 +739,7 @@ namespace ts.server {
*/
/*@internal*/
watchWildcardDirectory(directory: Path, flags: WatchDirectoryFlags, project: ConfiguredProject) {
return this.watchDirectory(
return this.watchFactory.watchDirectory(
this.host,
directory,
fileOrDirectory => {
@@ -1097,10 +1077,11 @@ namespace ts.server {
canonicalConfigFilePath: string,
configFileExistenceInfo: ConfigFileExistenceInfo
) {
configFileExistenceInfo.configFileWatcherForRootOfInferredProject = this.watchFile(
configFileExistenceInfo.configFileWatcherForRootOfInferredProject = this.watchFactory.watchFile(
this.host,
configFileName,
(_filename, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind),
PollingInterval.High,
WatchType.ConfigFileForInferredRoot
);
this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.UpdatedCallback);
@@ -1481,10 +1462,11 @@ namespace ts.server {
project.configFileSpecs = configFileSpecs;
// TODO: We probably should also watch the configFiles that are extended
project.configFileWatcher = this.watchFile(
project.configFileWatcher = this.watchFactory.watchFile(
this.host,
configFileName,
(_fileName, eventKind) => this.onConfigChangedForConfiguredProject(project, eventKind),
PollingInterval.High,
WatchType.ConfigFilePath,
project
);
@@ -1745,10 +1727,12 @@ namespace ts.server {
// do not watch files with mixed content - server doesn't know how to interpret it
if (!info.isDynamicOrHasMixedContent()) {
const { fileName } = info;
info.fileWatcher = this.watchFile(
info.fileWatcher = this.watchFactory.watchFilePath(
this.host,
fileName,
(_fileName, eventKind) => this.onSourceFileChanged(fileName, eventKind),
(fileName, eventKind, path) => this.onSourceFileChanged(fileName, eventKind, path),
PollingInterval.Medium,
info.path,
WatchType.ClosedScriptInfo
);
}

View File

@@ -398,7 +398,7 @@ namespace ts.server {
/*@internal*/
watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags) {
return this.projectService.watchDirectory(
return this.projectService.watchFactory.watchDirectory(
this.projectService.host,
directory,
cb,
@@ -415,7 +415,7 @@ namespace ts.server {
/*@internal*/
watchTypeRootsDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags) {
return this.projectService.watchDirectory(
return this.projectService.watchFactory.watchDirectory(
this.projectService.host,
directory,
cb,
@@ -907,7 +907,7 @@ namespace ts.server {
const oldExternalFiles = this.externalFiles || emptyArray as SortedReadonlyArray<string>;
this.externalFiles = this.getExternalFiles();
enumerateInsertsAndDeletes(this.externalFiles, oldExternalFiles,
enumerateInsertsAndDeletes(this.externalFiles, oldExternalFiles, compareStringsCaseSensitive,
// Ensure a ScriptInfo is created for new external files. This is performed indirectly
// by the LSHost for files in the program when the program is retrieved above but
// the program doesn't contain external files so this must be done explicitly.
@@ -915,8 +915,7 @@ namespace ts.server {
const scriptInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClient(inserted, this.currentDirectory, this.directoryStructureHost);
scriptInfo.attachToProject(this);
},
removed => this.detachScriptInfoFromProject(removed),
compareStringsCaseSensitive
removed => this.detachScriptInfoFromProject(removed)
);
const elapsed = timestamp() - start;
this.writeLog(`Finishing updateGraphWorker: Project: ${this.getProjectName()} Version: ${this.getProjectVersion()} structureChanged: ${hasChanges} Elapsed: ${elapsed}ms`);
@@ -932,7 +931,7 @@ namespace ts.server {
}
private addMissingFileWatcher(missingFilePath: Path) {
const fileWatcher = this.projectService.watchFile(
const fileWatcher = this.projectService.watchFactory.watchFile(
this.projectService.host,
missingFilePath,
(fileName, eventKind) => {
@@ -948,6 +947,7 @@ namespace ts.server {
this.projectService.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(this);
}
},
PollingInterval.Medium,
WatchType.MissingFilePath,
this
);

View File

@@ -685,11 +685,11 @@ namespace ts.server {
return;
}
fs.stat(watchedFile.fileName, (err: any, stats: any) => {
fs.stat(watchedFile.fileName, (err, stats) => {
if (err) {
if (err.code === "ENOENT") {
if (watchedFile.mtime.getTime() !== 0) {
watchedFile.mtime = new Date(0);
watchedFile.mtime = missingFileModifiedTime;
watchedFile.callback(watchedFile.fileName, FileWatcherEventKind.Deleted);
}
}
@@ -698,17 +698,7 @@ namespace ts.server {
}
}
else {
const oldTime = watchedFile.mtime.getTime();
const newTime = stats.mtime.getTime();
if (oldTime !== newTime) {
watchedFile.mtime = stats.mtime;
const eventKind = oldTime === 0
? FileWatcherEventKind.Created
: newTime === 0
? FileWatcherEventKind.Deleted
: FileWatcherEventKind.Changed;
watchedFile.callback(watchedFile.fileName, eventKind);
}
onWatchedFileStat(watchedFile, stats.mtime);
}
});
}
@@ -742,7 +732,7 @@ namespace ts.server {
callback,
mtime: sys.fileExists(fileName)
? getModifiedTime(fileName)
: new Date(0) // Any subsequent modification will occur after this time
: missingFileModifiedTime // Any subsequent modification will occur after this time
};
watchedFiles.push(file);

View File

@@ -264,36 +264,6 @@ namespace ts.server {
return index === 0 || value !== array[index - 1];
}
export function enumerateInsertsAndDeletes<T>(newItems: SortedReadonlyArray<T>, oldItems: SortedReadonlyArray<T>, inserted: (newItem: T) => void, deleted: (oldItem: T) => void, comparer: Comparer<T>) {
let newIndex = 0;
let oldIndex = 0;
const newLen = newItems.length;
const oldLen = oldItems.length;
while (newIndex < newLen && oldIndex < oldLen) {
const newItem = newItems[newIndex];
const oldItem = oldItems[oldIndex];
const compareResult = comparer(newItem, oldItem);
if (compareResult === Comparison.LessThan) {
inserted(newItem);
newIndex++;
}
else if (compareResult === Comparison.GreaterThan) {
deleted(oldItem);
oldIndex++;
}
else {
newIndex++;
oldIndex++;
}
}
while (newIndex < newLen) {
inserted(newItems[newIndex++]);
}
while (oldIndex < oldLen) {
deleted(oldItems[oldIndex++]);
}
}
/* @internal */
export function indent(str: string): string {
return "\n " + str;