Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
160 changes: 86 additions & 74 deletions src/compiler/sys.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,8 +35,7 @@ namespace ts {

export type FileWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind, modifiedTime?: Date) => void;
export type DirectoryWatcherCallback = (fileName: string) => void;
/*@internal*/
export interface WatchedFile {
interface WatchedFile {
readonly fileName: string;
readonly callback: FileWatcherCallback;
mtime: Date;
Expand DownExpand Up@@ -81,8 +80,7 @@ namespace ts {
/* @internal */
export let unchangedPollThresholds = createPollingIntervalBasedLevels(defaultChunkLevels);

/* @internal */
export function setCustomPollingValues(system: System) {
function setCustomPollingValues(system: System) {
if (!system.getEnvironmentVariable) {
return;
}
Expand DownExpand Up@@ -189,31 +187,28 @@ namespace ts {
}
}

/* @internal */
export function createDynamicPriorityPollingWatchFile(host: {
interface WatchedFileWithUnchangedPolls extends WatchedFileWithIsClosed {
unchangedPolls: number;
}
function createDynamicPriorityPollingWatchFile(host: {
getModifiedTime: NonNullable<System["getModifiedTime"]>;
setTimeout: NonNullable<System["setTimeout"]>;
}): HostWatchFile {
interface WatchedFile extends ts.WatchedFile {
isClosed?: boolean;
unchangedPolls: number;
}

interface PollingIntervalQueue extends Array<WatchedFile> {
interface PollingIntervalQueue extends Array<WatchedFileWithUnchangedPolls> {
pollingInterval: PollingInterval;
pollIndex: number;
pollScheduled: boolean;
}

const watchedFiles: WatchedFile[] = [];
const changedFilesInLastPoll: WatchedFile[] = [];
const watchedFiles: WatchedFileWithUnchangedPolls[] = [];
const changedFilesInLastPoll: WatchedFileWithUnchangedPolls[] = [];
const lowPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.Low);
const mediumPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.Medium);
const highPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.High);
return watchFile;

function watchFile(fileName: string, callback: FileWatcherCallback, defaultPollingInterval: PollingInterval): FileWatcher {
const file: WatchedFile = {
const file: WatchedFileWithUnchangedPolls = {
fileName,
callback,
unchangedPolls: 0,
Expand All@@ -233,7 +228,7 @@ namespace ts {
}

function createPollingIntervalQueue(pollingInterval: PollingInterval): PollingIntervalQueue {
const queue = [] as WatchedFile[] as PollingIntervalQueue;
const queue = [] as WatchedFileWithUnchangedPolls[] as PollingIntervalQueue;
queue.pollingInterval = pollingInterval;
queue.pollIndex = 0;
queue.pollScheduled = false;
Expand DownExpand Up@@ -265,7 +260,7 @@ namespace ts {
}
}

function pollQueue(queue: (WatchedFile | undefined)[], pollingInterval: PollingInterval, pollIndex: number, chunkSize: number) {
function pollQueue(queue: (WatchedFileWithUnchangedPolls | undefined)[], pollingInterval: PollingInterval, pollIndex: number, chunkSize: number) {
return pollWatchedFileQueue(
host,
queue,
Expand All@@ -274,7 +269,7 @@ namespace ts {
onWatchFileStat
);

function onWatchFileStat(watchedFile: WatchedFile, pollIndex: number, fileChanged: boolean) {
function onWatchFileStat(watchedFile: WatchedFileWithUnchangedPolls, pollIndex: number, fileChanged: boolean) {
if (fileChanged) {
watchedFile.unchangedPolls = 0;
// Changed files go to changedFilesInLastPoll queue
Expand DownExpand Up@@ -311,12 +306,12 @@ namespace ts {
}
}

function addToPollingIntervalQueue(file: WatchedFile, pollingInterval: PollingInterval) {
function addToPollingIntervalQueue(file: WatchedFileWithUnchangedPolls, pollingInterval: PollingInterval) {
pollingIntervalQueue(pollingInterval).push(file);
scheduleNextPollIfNotAlreadyScheduled(pollingInterval);
}

function addChangedFileToLowPollingIntervalQueue(file: WatchedFile) {
function addChangedFileToLowPollingIntervalQueue(file: WatchedFileWithUnchangedPolls) {
changedFilesInLastPoll.push(file);
scheduleNextPollIfNotAlreadyScheduled(PollingInterval.Low);
}
Expand DownExpand Up@@ -423,59 +418,50 @@ namespace ts {
}
}

/* @internal */
export function createSingleFileWatcherPerName(
watchFile: HostWatchFile,
useCaseSensitiveFileNames: boolean
): HostWatchFile {
interface SingleFileWatcher {
watcher: FileWatcher;
refCount: number;
}
const cache = new Map<string, SingleFileWatcher>();
const callbacksCache = createMultiMap<FileWatcherCallback>();
interface SingleFileWatcher<T extends FileWatcherCallback | FsWatchCallback>{
watcher: FileWatcher;
callbacks: T[];
}
function createSingleWatcherPerName<T extends FileWatcherCallback | FsWatchCallback>(
cache: Map<SingleFileWatcher<T>>,
useCaseSensitiveFileNames: boolean,
name: string,
callback: T,
createWatcher: (callback: T) => FileWatcher,
): FileWatcher {
const toCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
const path = toCanonicalFileName(name);
const existing = cache.get(path);
if (existing) {
existing.callbacks.push(callback);
}
else {
cache.set(path, {
watcher: createWatcher((
// Cant infer types correctly so lets satisfy checker
(param1: any, param2: never, param3: any) => cache.get(path)?.callbacks.slice().forEach(cb => cb(param1, param2, param3))
Comment thread
sheetalkamat marked this conversation as resolved.
Comment thread
sheetalkamat marked this conversation as resolved.
) as T),
callbacks: [callback]
});
}

return (fileName, callback, pollingInterval, options) => {
const path = toCanonicalFileName(fileName);
const existing = cache.get(path);
if (existing) {
existing.refCount++;
}
else {
cache.set(path, {
watcher: watchFile(
fileName,
(fileName, eventKind, modifiedTime) => forEach(
callbacksCache.get(path),
cb => cb(fileName, eventKind, modifiedTime)
),
pollingInterval,
options
),
refCount: 1
});
return {
close: () => {
const watcher = cache.get(path);
// Watcher is not expected to be undefined, but if it is normally its because
// exception was thrown somewhere else and watch state is not what it should be
if (!watcher) return;
Comment thread
sheetalkamat marked this conversation as resolved.
if (!orderedRemoveItem(watcher.callbacks, callback) || watcher.callbacks.length) return;
Comment thread
sheetalkamat marked this conversation as resolved.
Comment thread
sheetalkamat marked this conversation as resolved.
cache.delete(path);
closeFileWatcherOf(watcher);
}
callbacksCache.add(path, callback);

return {
close: () => {
const watcher = Debug.checkDefined(cache.get(path));
callbacksCache.remove(path, callback);
watcher.refCount--;
if (watcher.refCount) return;
cache.delete(path);
closeFileWatcherOf(watcher);
}
};
};
}

/**
* Returns true if file status changed
*/
/*@internal*/
export function onWatchedFileStat(watchedFile: WatchedFile, modifiedTime: Date): boolean {
function onWatchedFileStat(watchedFile: WatchedFile, modifiedTime: Date): boolean {
const oldTime = watchedFile.mtime.getTime();
const newTime = modifiedTime.getTime();
if (oldTime !== newTime) {
Expand DownExpand Up@@ -512,8 +498,7 @@ namespace ts {
curSysLog = logger;
}

/*@internal*/
export interface RecursiveDirectoryWatcherHost {
interface RecursiveDirectoryWatcherHost {
watchDirectory: HostWatchDirectory;
useCaseSensitiveFileNames: boolean;
getCurrentDirectory: System["getCurrentDirectory"];
Expand All@@ -529,8 +514,7 @@ namespace ts {
* that means if this is recursive watcher, watch the children directories as well
* (eg on OS that dont support recursive watch using fs.watch use fs.watchFile)
*/
/*@internal*/
export function createDirectoryWatcherSupportingRecursive({
function createDirectoryWatcherSupportingRecursive({
watchDirectory,
useCaseSensitiveFileNames,
getCurrentDirectory,
Expand DownExpand Up@@ -792,8 +776,7 @@ namespace ts {
Directory,
}

/*@internal*/
export function createFileWatcherCallback(callback: FsWatchCallback): FileWatcherCallback {
function createFileWatcherCallback(callback: FsWatchCallback): FileWatcherCallback {
return (_fileName, eventKind, modifiedTime) => callback(eventKind === FileWatcherEventKind.Changed ? "change" : "rename", "", modifiedTime);
}

Expand DownExpand Up@@ -854,7 +837,7 @@ namespace ts {
/*@internal*/
export interface CreateSystemWatchFunctions {
// Polling watch file
pollingWatchFile: HostWatchFile;
pollingWatchFileWorker: HostWatchFile;
// For dynamic polling watch file
getModifiedTime: NonNullable<System["getModifiedTime"]>;
setTimeout: NonNullable<System["setTimeout"]>;
Expand All@@ -878,7 +861,7 @@ namespace ts {

/*@internal*/
export function createSystemWatchFunctions({
pollingWatchFile,
pollingWatchFileWorker,
getModifiedTime,
setTimeout,
clearTimeout,
Expand All@@ -896,6 +879,9 @@ namespace ts {
inodeWatching,
sysLog,
}: CreateSystemWatchFunctions): { watchFile: HostWatchFile; watchDirectory: HostWatchDirectory; } {
const pollingWatches = new Map<string, SingleFileWatcher<FileWatcherCallback>>();
const fsWatches = new Map<string, SingleFileWatcher<FsWatchCallback>>();
const fsWatchesRecursive = new Map<string, SingleFileWatcher<FsWatchCallback>>();
let dynamicPollingWatchFile: HostWatchFile | undefined;
let fixedChunkSizePollingWatchFile: HostWatchFile | undefined;
let nonPollingWatchFile: HostWatchFile | undefined;
Expand DownExpand Up@@ -968,7 +954,7 @@ namespace ts {
// Use notifications from FS to watch with falling back to fs.watchFile
generateWatchFileOptions(WatchFileKind.UseFsEventsOnParentDirectory, PollingWatchKind.PriorityInterval, options) :
// Default to do not use fixed polling interval
{ watchFile: defaultWatchFileKind?.() || WatchFileKind.FixedPollingInterval };
{ watchFile: defaultWatchFileKind?.() || WatchFileKind.UseFsEvents };
}
}

Expand DownExpand Up@@ -1073,13 +1059,39 @@ namespace ts {
}
}

function pollingWatchFile(fileName: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined) {
return createSingleWatcherPerName(
pollingWatches,
useCaseSensitiveFileNames,
fileName,
callback,
cb => pollingWatchFileWorker(fileName, cb, pollingInterval, options),
);
}
function fsWatch(
fileOrDirectory: string,
entryKind: FileSystemEntryKind,
callback: FsWatchCallback,
recursive: boolean,
fallbackPollingInterval: PollingInterval,
fallbackOptions: WatchOptions | undefined
): FileWatcher {
return createSingleWatcherPerName(
recursive ? fsWatchesRecursive : fsWatches,
useCaseSensitiveFileNames,
fileOrDirectory,
callback,
cb => fsWatchHandlingExistenceOnHost(fileOrDirectory, entryKind, cb, recursive, fallbackPollingInterval, fallbackOptions),
);
}

function fsWatchHandlingExistenceOnHost(
fileOrDirectory: string,
entryKind: FileSystemEntryKind,
callback: FsWatchCallback,
recursive: boolean,
fallbackPollingInterval: PollingInterval,
fallbackOptions: WatchOptions | undefined
): FileWatcher {
let lastDirectoryPartWithDirectorySeparator: string | undefined;
let lastDirectoryPart: string | undefined;
Expand DownExpand Up@@ -1445,7 +1457,7 @@ namespace ts {
const fsSupportsRecursiveFsWatch = isNode4OrLater && (process.platform === "win32" || process.platform === "darwin");
const getCurrentDirectory = memoize(() => process.cwd());
const { watchFile, watchDirectory } = createSystemWatchFunctions({
pollingWatchFile: createSingleFileWatcherPerName(fsWatchFileWorker, useCaseSensitiveFileNames),
pollingWatchFileWorker: fsWatchFileWorker,
getModifiedTime,
setTimeout,
clearTimeout,
Expand Down
Loading