mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-23 17:30:04 -05:00
Merge pull request #21243 from Microsoft/watchOptions
Different watchFile and watchDirectory options through environment variable
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user