Add way to exclude files and directories to watch (#39243)

* Parse excludeDirectories and excludeFiles

* Use watch factory in typings installer

* Some refactoring for watchFactory

* Create Noop watcher if file or directory being watched is excluded

* Baselines without using exclude watch options

* Baselines including exclude option

* Handle exclude options in the system watches

* Add test without exclude option for recursive directory watching

* Test baselines with exclude option

* Always set sysLog

* Test for exclude option in server

* Add exclude options in the config file and fix the test

* Fix host configuration for server

* Handle host configuration for watch options

* Fix sysLog time log so baselines can be clean

* Handle reloadProjects to reload the project from scratch

* Ensure that file updates are reflected

* Feedback

* Feedback
This commit is contained in:
Sheetal Nandi
2020-11-04 13:30:06 -08:00
committed by GitHub
parent 6c6315db58
commit 76cf8fd78b
29 changed files with 1912 additions and 275 deletions
+107 -33
View File
@@ -133,6 +133,30 @@ namespace ts {
category: Diagnostics.Advanced_Options,
description: Diagnostics.Synchronously_call_callbacks_and_update_the_state_of_directory_watchers_on_platforms_that_don_t_support_recursive_watching_natively,
},
{
name: "excludeDirectories",
type: "list",
element: {
name: "excludeDirectory",
type: "string",
isFilePath: true,
extraValidation: specToDiagnostic
},
category: Diagnostics.Advanced_Options,
description: Diagnostics.Synchronously_call_callbacks_and_update_the_state_of_directory_watchers_on_platforms_that_don_t_support_recursive_watching_natively,
},
{
name: "excludeFiles",
type: "list",
element: {
name: "excludeFile",
type: "string",
isFilePath: true,
extraValidation: specToDiagnostic
},
category: Diagnostics.Advanced_Options,
description: Diagnostics.Synchronously_call_callbacks_and_update_the_state_of_directory_watchers_on_platforms_that_don_t_support_recursive_watching_natively,
},
];
/* @internal */
@@ -1231,9 +1255,9 @@ namespace ts {
const values = value.split(",");
switch (opt.element.type) {
case "number":
return map(values, parseInt);
return mapDefined(values, v => validateJsonOptionValue(opt.element, parseInt(v), errors));
case "string":
return map(values, v => v || "");
return mapDefined(values, v => validateJsonOptionValue(opt.element, v || "", errors));
default:
return mapDefined(values, v => parseCustomTypeOption(<CommandLineOptionOfCustomType>opt.element, v, errors));
}
@@ -1363,7 +1387,7 @@ namespace ts {
}
else if (opt.type === "boolean") {
if (optValue === "false") {
options[opt.name] = false;
options[opt.name] = validateJsonOptionValue(opt, /*value*/ false, errors);
i++;
}
else {
@@ -1385,20 +1409,20 @@ namespace ts {
if (args[i] !== "null") {
switch (opt.type) {
case "number":
options[opt.name] = parseInt(args[i]);
options[opt.name] = validateJsonOptionValue(opt, parseInt(args[i]), errors);
i++;
break;
case "boolean":
// boolean flag has optional value true, false, others
const optValue = args[i];
options[opt.name] = optValue !== "false";
options[opt.name] = validateJsonOptionValue(opt, optValue !== "false", errors);
// consume next argument as boolean flag value
if (optValue === "false" || optValue === "true") {
i++;
}
break;
case "string":
options[opt.name] = args[i] || "";
options[opt.name] = validateJsonOptionValue(opt, args[i] || "", errors);
i++;
break;
case "list":
@@ -1843,9 +1867,10 @@ namespace ts {
function convertArrayLiteralExpressionToJson(
elements: NodeArray<Expression>,
elementOption: CommandLineOption | undefined
): any[] | void {
) {
if (!returnValue) {
return elements.forEach(element => convertPropertyValueToJson(element, elementOption));
elements.forEach(element => convertPropertyValueToJson(element, elementOption));
return undefined;
}
// Filter out invalid values
@@ -1853,18 +1878,19 @@ namespace ts {
}
function convertPropertyValueToJson(valueExpression: Expression, option: CommandLineOption | undefined): any {
let invalidReported: boolean | undefined;
switch (valueExpression.kind) {
case SyntaxKind.TrueKeyword:
reportInvalidOptionValue(option && option.type !== "boolean");
return true;
return validateValue(/*value*/ true);
case SyntaxKind.FalseKeyword:
reportInvalidOptionValue(option && option.type !== "boolean");
return false;
return validateValue(/*value*/ false);
case SyntaxKind.NullKeyword:
reportInvalidOptionValue(option && option.name === "extends"); // "extends" is the only option we don't allow null/undefined for
return null; // eslint-disable-line no-null/no-null
return validateValue(/*value*/ null); // eslint-disable-line no-null/no-null
case SyntaxKind.StringLiteral:
if (!isDoubleQuotedString(valueExpression)) {
@@ -1882,20 +1908,21 @@ namespace ts {
(message, arg0, arg1) => createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, message, arg0, arg1)
)
);
invalidReported = true;
}
}
return text;
return validateValue(text);
case SyntaxKind.NumericLiteral:
reportInvalidOptionValue(option && option.type !== "number");
return Number((<NumericLiteral>valueExpression).text);
return validateValue(Number((<NumericLiteral>valueExpression).text));
case SyntaxKind.PrefixUnaryExpression:
if ((<PrefixUnaryExpression>valueExpression).operator !== SyntaxKind.MinusToken || (<PrefixUnaryExpression>valueExpression).operand.kind !== SyntaxKind.NumericLiteral) {
break; // not valid JSON syntax
}
reportInvalidOptionValue(option && option.type !== "number");
return -Number((<NumericLiteral>(<PrefixUnaryExpression>valueExpression).operand).text);
return validateValue(-Number((<NumericLiteral>(<PrefixUnaryExpression>valueExpression).operand).text));
case SyntaxKind.ObjectLiteralExpression:
reportInvalidOptionValue(option && option.type !== "object");
@@ -1909,20 +1936,20 @@ namespace ts {
// If need arises, we can modify this interface and callbacks as needed
if (option) {
const { elementOptions, extraKeyDiagnostics, name: optionName } = <TsConfigOnlyOption>option;
return convertObjectLiteralExpressionToJson(objectLiteralExpression,
elementOptions, extraKeyDiagnostics, optionName);
return validateValue(convertObjectLiteralExpressionToJson(objectLiteralExpression,
elementOptions, extraKeyDiagnostics, optionName));
}
else {
return convertObjectLiteralExpressionToJson(
return validateValue(convertObjectLiteralExpressionToJson(
objectLiteralExpression, /* knownOptions*/ undefined,
/*extraKeyDiagnosticMessage */ undefined, /*parentOption*/ undefined);
/*extraKeyDiagnosticMessage */ undefined, /*parentOption*/ undefined));
}
case SyntaxKind.ArrayLiteralExpression:
reportInvalidOptionValue(option && option.type !== "list");
return convertArrayLiteralExpressionToJson(
return validateValue(convertArrayLiteralExpressionToJson(
(<ArrayLiteralExpression>valueExpression).elements,
option && (<CommandLineOptionOfListType>option).element);
option && (<CommandLineOptionOfListType>option).element));
}
// Not in expected format
@@ -1935,9 +1962,21 @@ namespace ts {
return undefined;
function validateValue(value: CompilerOptionsValue) {
if (!invalidReported) {
const diagnostic = option?.extraValidation?.(value);
if (diagnostic) {
errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, ...diagnostic));
return undefined;
}
}
return value;
}
function reportInvalidOptionValue(isError: boolean | undefined) {
if (isError) {
errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.Compiler_option_0_requires_a_value_of_type_1, option!.name, getCompilerOptionValueTypeString(option!)));
invalidReported = true;
}
}
}
@@ -2846,7 +2885,8 @@ namespace ts {
return defaultOptions;
}
function convertJsonOption(opt: CommandLineOption, value: any, basePath: string, errors: Push<Diagnostic>): CompilerOptionsValue {
/*@internal*/
export function convertJsonOption(opt: CommandLineOption, value: any, basePath: string, errors: Push<Diagnostic>): CompilerOptionsValue {
if (isCompilerOptionsValue(opt, value)) {
const optType = opt.type;
if (optType === "list" && isArray(value)) {
@@ -2855,7 +2895,8 @@ namespace ts {
else if (!isString(optType)) {
return convertJsonOptionOfCustomType(<CommandLineOptionOfCustomType>opt, <string>value, errors);
}
return normalizeNonListOptionValue(opt, basePath, value);
const validatedValue = validateJsonOptionValue(opt, value, errors);
return isNullOrUndefined(validatedValue) ? validatedValue : normalizeNonListOptionValue(opt, basePath, validatedValue);
}
else {
errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, opt.name, getCompilerOptionValueTypeString(opt)));
@@ -2887,12 +2928,20 @@ namespace ts {
return value;
}
function validateJsonOptionValue<T extends CompilerOptionsValue>(opt: CommandLineOption, value: T, errors: Push<Diagnostic>): T | undefined {
if (isNullOrUndefined(value)) return undefined;
const d = opt.extraValidation?.(value);
if (!d) return value;
errors.push(createCompilerDiagnostic(...d));
return undefined;
}
function convertJsonOptionOfCustomType(opt: CommandLineOptionOfCustomType, value: string, errors: Push<Diagnostic>) {
if (isNullOrUndefined(value)) return undefined;
const key = value.toLowerCase();
const val = opt.type.get(key);
if (val !== undefined) {
return val;
return validateJsonOptionValue(opt, val, errors);
}
else {
errors.push(createCompilerDiagnosticForInvalidCustomType(opt));
@@ -2994,11 +3043,11 @@ namespace ts {
// file system.
if (includeSpecs) {
validatedIncludeSpecs = validateSpecs(includeSpecs, errors, /*allowTrailingRecursion*/ false, jsonSourceFile, "include");
validatedIncludeSpecs = validateSpecs(includeSpecs, errors, /*disallowTrailingRecursion*/ true, jsonSourceFile, "include");
}
if (excludeSpecs) {
validatedExcludeSpecs = validateSpecs(excludeSpecs, errors, /*allowTrailingRecursion*/ true, jsonSourceFile, "exclude");
validatedExcludeSpecs = validateSpecs(excludeSpecs, errors, /*disallowTrailingRecursion*/ false, jsonSourceFile, "exclude");
}
// Wildcard directories (provided as part of a wildcard path) are stored in a
@@ -3142,19 +3191,44 @@ namespace ts {
}
}
const excludePattern = getRegularExpressionForWildcard(validatedExcludeSpecs, combinePaths(normalizePath(currentDirectory), basePath), "exclude");
return matchesExcludeWorker(pathToCheck, validatedExcludeSpecs, useCaseSensitiveFileNames, currentDirectory, basePath);
}
/* @internal */
export function matchesExclude(
pathToCheck: string,
excludeSpecs: readonly string[] | undefined,
useCaseSensitiveFileNames: boolean,
currentDirectory: string
) {
return matchesExcludeWorker(
pathToCheck,
filter(excludeSpecs, spec => !invalidDotDotAfterRecursiveWildcardPattern.test(spec)),
useCaseSensitiveFileNames,
currentDirectory
);
}
function matchesExcludeWorker(
pathToCheck: string,
excludeSpecs: readonly string[] | undefined,
useCaseSensitiveFileNames: boolean,
currentDirectory: string,
basePath?: string
) {
const excludePattern = getRegularExpressionForWildcard(excludeSpecs, combinePaths(normalizePath(currentDirectory), basePath), "exclude");
const excludeRegex = excludePattern && getRegexFromPattern(excludePattern, useCaseSensitiveFileNames);
if (!excludeRegex) return false;
if (excludeRegex.test(pathToCheck)) return true;
return !hasExtension(pathToCheck) && excludeRegex.test(ensureTrailingDirectorySeparator(pathToCheck));
}
function validateSpecs(specs: readonly string[], errors: Push<Diagnostic>, allowTrailingRecursion: boolean, jsonSourceFile: TsConfigSourceFile | undefined, specKey: string): readonly string[] {
function validateSpecs(specs: readonly string[], errors: Push<Diagnostic>, disallowTrailingRecursion: boolean, jsonSourceFile: TsConfigSourceFile | undefined, specKey: string): readonly string[] {
return specs.filter(spec => {
if (!isString(spec)) return false;
const diag = specToDiagnostic(spec, allowTrailingRecursion);
const diag = specToDiagnostic(spec, disallowTrailingRecursion);
if (diag !== undefined) {
errors.push(createDiagnostic(diag, spec));
errors.push(createDiagnostic(...diag));
}
return diag === undefined;
});
@@ -3167,12 +3241,12 @@ namespace ts {
}
}
function specToDiagnostic(spec: string, allowTrailingRecursion: boolean): DiagnosticMessage | undefined {
if (!allowTrailingRecursion && invalidTrailingRecursionPattern.test(spec)) {
return Diagnostics.File_specification_cannot_end_in_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0;
function specToDiagnostic(spec: string, disallowTrailingRecursion?: boolean): [DiagnosticMessage, string] | undefined {
if (disallowTrailingRecursion && invalidTrailingRecursionPattern.test(spec)) {
return [Diagnostics.File_specification_cannot_end_in_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, spec];
}
else if (invalidDotDotAfterRecursiveWildcardPattern.test(spec)) {
return Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0;
return [Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, spec];
}
}
+61 -26
View File
@@ -449,6 +449,7 @@ namespace ts {
export interface RecursiveDirectoryWatcherHost {
watchDirectory: HostWatchDirectory;
useCaseSensitiveFileNames: boolean;
getCurrentDirectory: System["getCurrentDirectory"];
getAccessibleSortedChildDirectories(path: string): readonly string[];
directoryExists(dir: string): boolean;
realpath(s: string): string;
@@ -462,7 +463,16 @@ namespace ts {
* (eg on OS that dont support recursive watch using fs.watch use fs.watchFile)
*/
/*@internal*/
export function createDirectoryWatcherSupportingRecursive(host: RecursiveDirectoryWatcherHost): HostWatchDirectory {
export function createDirectoryWatcherSupportingRecursive({
watchDirectory,
useCaseSensitiveFileNames,
getCurrentDirectory,
getAccessibleSortedChildDirectories,
directoryExists,
realpath,
setTimeout,
clearTimeout
}: RecursiveDirectoryWatcherHost): HostWatchDirectory {
interface ChildDirectoryWatcher extends FileWatcher {
dirName: string;
}
@@ -478,12 +488,12 @@ namespace ts {
const cacheToUpdateChildWatches = new Map<Path, { dirName: string; options: WatchOptions | undefined; fileNames: string[]; }>();
let timerToUpdateChildWatches: any;
const filePathComparer = getStringComparer(!host.useCaseSensitiveFileNames);
const toCanonicalFilePath = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
const filePathComparer = getStringComparer(!useCaseSensitiveFileNames);
const toCanonicalFilePath = createGetCanonicalFileName(useCaseSensitiveFileNames);
return (dirName, callback, recursive, options) => recursive ?
createDirectoryWatcher(dirName, options, callback) :
host.watchDirectory(dirName, callback, recursive, options);
watchDirectory(dirName, callback, recursive, options);
/**
* Create the directory watcher for the dirPath.
@@ -496,8 +506,8 @@ namespace ts {
}
else {
directoryWatcher = {
watcher: host.watchDirectory(dirName, fileName => {
if (isIgnoredPath(fileName)) return;
watcher: watchDirectory(dirName, fileName => {
if (isIgnoredPath(fileName, options)) return;
if (options?.synchronousWatchDirectory) {
// Call the actual callback
@@ -578,7 +588,7 @@ namespace ts {
function nonSyncUpdateChildWatches(dirName: string, dirPath: Path, fileName: string, options: WatchOptions | undefined) {
// Iterate through existing children and update the watches if needed
const parentWatcher = cache.get(dirPath);
if (parentWatcher && host.directoryExists(dirName)) {
if (parentWatcher && directoryExists(dirName)) {
// Schedule the update and postpone invoke for callbacks
scheduleUpdateChildWatches(dirName, dirPath, fileName, options);
return;
@@ -598,10 +608,10 @@ namespace ts {
cacheToUpdateChildWatches.set(dirPath, { dirName, options, fileNames: [fileName] });
}
if (timerToUpdateChildWatches) {
host.clearTimeout(timerToUpdateChildWatches);
clearTimeout(timerToUpdateChildWatches);
timerToUpdateChildWatches = undefined;
}
timerToUpdateChildWatches = host.setTimeout(onTimerToUpdateChildWatches, 1000);
timerToUpdateChildWatches = setTimeout(onTimerToUpdateChildWatches, 1000);
}
function onTimerToUpdateChildWatches() {
@@ -620,7 +630,7 @@ namespace ts {
invokeCallbacks(dirPath, invokeMap, hasChanges ? undefined : fileNames);
}
sysLog(`sysLog:: invokingWatchers:: ${timestamp() - start}ms:: ${cacheToUpdateChildWatches.size}`);
sysLog(`sysLog:: invokingWatchers:: Elapsed:: ${timestamp() - start}ms:: ${cacheToUpdateChildWatches.size}`);
callbackCache.forEach((callbacks, rootDirName) => {
const existing = invokeMap.get(rootDirName);
if (existing) {
@@ -636,7 +646,7 @@ namespace ts {
});
const elapsed = timestamp() - start;
sysLog(`sysLog:: Elapsed ${elapsed}ms:: onTimerToUpdateChildWatches:: ${cacheToUpdateChildWatches.size} ${timerToUpdateChildWatches}`);
sysLog(`sysLog:: Elapsed:: ${elapsed}ms:: onTimerToUpdateChildWatches:: ${cacheToUpdateChildWatches.size} ${timerToUpdateChildWatches}`);
}
function removeChildWatches(parentWatcher: HostDirectoryWatcher | undefined) {
@@ -655,11 +665,11 @@ namespace ts {
if (!parentWatcher) return false;
let newChildWatches: ChildDirectoryWatcher[] | undefined;
const hasChanges = enumerateInsertsAndDeletes<string, ChildDirectoryWatcher>(
host.directoryExists(parentDir) ? mapDefined(host.getAccessibleSortedChildDirectories(parentDir), child => {
directoryExists(parentDir) ? mapDefined(getAccessibleSortedChildDirectories(parentDir), child => {
const childFullName = getNormalizedAbsolutePath(child, parentDir);
// Filter our the symbolic link directories since those arent included in recursive watch
// which is same behaviour when recursive: true is passed to fs.watch
return !isIgnoredPath(childFullName) && filePathComparer(childFullName, normalizePath(host.realpath(childFullName))) === Comparison.EqualTo ? childFullName : undefined;
return !isIgnoredPath(childFullName, options) && filePathComparer(childFullName, normalizePath(realpath(childFullName))) === Comparison.EqualTo ? childFullName : undefined;
}) : emptyArray,
parentWatcher.childWatches,
(child, childWatcher) => filePathComparer(child, childWatcher.dirName),
@@ -686,13 +696,14 @@ namespace ts {
}
}
function isIgnoredPath(path: string) {
return some(ignoredPaths, searchPath => isInPath(path, searchPath));
function isIgnoredPath(path: string, options: WatchOptions | undefined) {
return some(ignoredPaths, searchPath => isInPath(path, searchPath)) ||
isIgnoredByWatchOptions(path, options, useCaseSensitiveFileNames, getCurrentDirectory);
}
function isInPath(path: string, searchPath: string) {
if (stringContains(path, searchPath)) return true;
if (host.useCaseSensitiveFileNames) return false;
if (useCaseSensitiveFileNames) return false;
return stringContains(toCanonicalFilePath(path), searchPath);
}
}
@@ -729,14 +740,35 @@ namespace ts {
};
}
function createFsWatchCallbackForDirectoryWatcherCallback(directoryName: string, callback: DirectoryWatcherCallback): FsWatchCallback {
function isIgnoredByWatchOptions(
pathToCheck: string,
options: WatchOptions | undefined,
useCaseSensitiveFileNames: boolean,
getCurrentDirectory: System["getCurrentDirectory"],
) {
return (options?.excludeDirectories || options?.excludeFiles) && (
matchesExclude(pathToCheck, options?.excludeFiles, useCaseSensitiveFileNames, getCurrentDirectory()) ||
matchesExclude(pathToCheck, options?.excludeDirectories, useCaseSensitiveFileNames, getCurrentDirectory())
);
}
function createFsWatchCallbackForDirectoryWatcherCallback(
directoryName: string,
callback: DirectoryWatcherCallback,
options: WatchOptions | undefined,
useCaseSensitiveFileNames: boolean,
getCurrentDirectory: System["getCurrentDirectory"],
): FsWatchCallback {
return (eventName, relativeFileName) => {
// In watchDirectory we only care about adding and removing files (when event name is
// "rename"); changes made within files are handled by corresponding fileWatchers (when
// event name is "change")
if (eventName === "rename") {
// When deleting a file, the passed baseFileName is null
callback(!relativeFileName ? directoryName : normalizePath(combinePaths(directoryName, relativeFileName)));
const fileName = !relativeFileName ? directoryName : normalizePath(combinePaths(directoryName, relativeFileName));
if (!relativeFileName || !isIgnoredByWatchOptions(fileName, options, useCaseSensitiveFileNames, getCurrentDirectory)) {
callback(fileName);
}
}
};
}
@@ -753,6 +785,7 @@ namespace ts {
fsWatch: FsWatch;
fileExists: System["fileExists"];
useCaseSensitiveFileNames: boolean;
getCurrentDirectory: System["getCurrentDirectory"];
fsSupportsRecursiveFsWatch: boolean;
directoryExists: System["directoryExists"];
getAccessibleSortedChildDirectories(path: string): readonly string[];
@@ -772,6 +805,7 @@ namespace ts {
fsWatch,
fileExists,
useCaseSensitiveFileNames,
getCurrentDirectory,
fsSupportsRecursiveFsWatch,
directoryExists,
getAccessibleSortedChildDirectories,
@@ -868,7 +902,7 @@ namespace ts {
return fsWatch(
directoryName,
FileSystemEntryKind.Directory,
createFsWatchCallbackForDirectoryWatcherCallback(directoryName, callback),
createFsWatchCallbackForDirectoryWatcherCallback(directoryName, callback, options, useCaseSensitiveFileNames, getCurrentDirectory),
recursive,
PollingInterval.Medium,
getFallbackOptions(options)
@@ -878,6 +912,7 @@ namespace ts {
if (!hostRecursiveDirectoryWatcher) {
hostRecursiveDirectoryWatcher = createDirectoryWatcherSupportingRecursive({
useCaseSensitiveFileNames,
getCurrentDirectory,
directoryExists,
getAccessibleSortedChildDirectories,
watchDirectory: nonRecursiveWatchDirectory,
@@ -891,8 +926,8 @@ namespace ts {
function nonRecursiveWatchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined): FileWatcher {
Debug.assert(!recursive);
options = updateOptionsForWatchDirectory(options);
const watchDirectoryKind = Debug.checkDefined(options.watchDirectory);
const watchDirectoryOptions = updateOptionsForWatchDirectory(options);
const watchDirectoryKind = Debug.checkDefined(watchDirectoryOptions.watchDirectory);
switch (watchDirectoryKind) {
case WatchDirectoryKind.FixedPollingInterval:
return pollingWatchFile(
@@ -912,10 +947,10 @@ namespace ts {
return fsWatch(
directoryName,
FileSystemEntryKind.Directory,
createFsWatchCallbackForDirectoryWatcherCallback(directoryName, callback),
createFsWatchCallbackForDirectoryWatcherCallback(directoryName, callback, options, useCaseSensitiveFileNames, getCurrentDirectory),
recursive,
PollingInterval.Medium,
getFallbackOptions(options)
getFallbackOptions(watchDirectoryOptions)
);
default:
Debug.assertNever(watchDirectoryKind);
@@ -1161,6 +1196,7 @@ namespace ts {
const platform: string = _os.platform();
const useCaseSensitiveFileNames = isFileSystemCaseSensitive();
const fsSupportsRecursiveFsWatch = isNode4OrLater && (process.platform === "win32" || process.platform === "darwin");
const getCurrentDirectory = memoize(() => process.cwd());
const { watchFile, watchDirectory } = createSystemWatchFunctions({
pollingWatchFile: createSingleFileWatcherPerName(fsWatchFileWorker, useCaseSensitiveFileNames),
getModifiedTime,
@@ -1168,6 +1204,7 @@ namespace ts {
clearTimeout,
fsWatch,
useCaseSensitiveFileNames,
getCurrentDirectory,
fileExists,
// Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows
// (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643)
@@ -1214,9 +1251,7 @@ namespace ts {
getExecutingFilePath() {
return __filename;
},
getCurrentDirectory() {
return process.cwd();
},
getCurrentDirectory,
getDirectories,
getEnvironmentVariable(name: string) {
return process.env[name] || "";
+4 -14
View File
@@ -209,7 +209,7 @@ namespace ts {
originalGetSourceFile: CompilerHost["getSourceFile"];
}
interface SolutionBuilderState<T extends BuilderProgram = BuilderProgram> {
interface SolutionBuilderState<T extends BuilderProgram = BuilderProgram> extends WatchFactory<WatchType, ResolvedConfigFileName> {
readonly host: SolutionBuilderHost<T>;
readonly hostWithWatch: SolutionBuilderWithWatchHost<T>;
readonly currentDirectory: string;
@@ -256,9 +256,6 @@ namespace ts {
timerToBuildInvalidatedProject: any;
reportFileChangeDetected: boolean;
watchFile: WatchFile<WatchType, ResolvedConfigFileName>;
watchFilePath: WatchFilePath<WatchType, ResolvedConfigFileName>;
watchDirectory: WatchDirectory<WatchType, ResolvedConfigFileName>;
writeLog: (s: string) => void;
}
@@ -282,7 +279,7 @@ namespace ts {
loadWithLocalCache<ResolvedModuleFull>(Debug.checkEachDefined(moduleNames), containingFile, redirectedReference, loader);
}
const { watchFile, watchFilePath, watchDirectory, writeLog } = createWatchFactory<ResolvedConfigFileName>(hostWithWatch, options);
const { watchFile, watchDirectory, writeLog } = createWatchFactory<ResolvedConfigFileName>(hostWithWatch, options);
const state: SolutionBuilderState<T> = {
host,
@@ -331,7 +328,6 @@ namespace ts {
timerToBuildInvalidatedProject: undefined,
reportFileChangeDetected: false,
watchFile,
watchFilePath,
watchDirectory,
writeLog,
};
@@ -1783,7 +1779,6 @@ namespace ts {
function watchConfigFile(state: SolutionBuilderState, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine | undefined) {
if (!state.watch || state.allWatchedConfigFiles.has(resolvedPath)) return;
state.allWatchedConfigFiles.set(resolvedPath, state.watchFile(
state.hostWithWatch,
resolved,
() => {
invalidateProjectAndScheduleBuilds(state, resolvedPath, ConfigFileProgramReloadLevel.Full);
@@ -1801,7 +1796,6 @@ namespace ts {
getOrCreateValueMapFromConfigFileMap(state.allWatchedWildcardDirectories, resolvedPath),
new Map(getEntries(parsed.configFileSpecs!.wildcardDirectories)),
(dir, flags) => state.watchDirectory(
state.hostWithWatch,
dir,
fileOrDirectory => {
if (isIgnoredFileFromWildCardWatching({
@@ -1833,13 +1827,11 @@ namespace ts {
getOrCreateValueMapFromConfigFileMap(state.allWatchedInputFiles, resolvedPath),
arrayToMap(parsed.fileNames, fileName => toPath(state, fileName)),
{
createNewValue: (path, input) => state.watchFilePath(
state.hostWithWatch,
createNewValue: (_path, input) => state.watchFile(
input,
() => invalidateProjectAndScheduleBuilds(state, resolvedPath, ConfigFileProgramReloadLevel.None),
PollingInterval.Low,
parsed?.watchOptions,
path as Path,
WatchType.SourceFile,
resolved
),
@@ -1914,9 +1906,7 @@ namespace ts {
}
function reportWatchStatus(state: SolutionBuilderState, message: DiagnosticMessage, ...args: (string | number | undefined)[]) {
if (state.hostWithWatch.onWatchStatusChange) {
state.hostWithWatch.onWatchStatusChange(createCompilerDiagnostic(message, ...args), state.host.getNewLine(), state.baseCompilerOptions);
}
state.hostWithWatch.onWatchStatusChange?.(createCompilerDiagnostic(message, ...args), state.host.getNewLine(), state.baseCompilerOptions);
}
function reportErrors({ host }: SolutionBuilderState, errors: readonly Diagnostic[]) {
+3
View File
@@ -5846,6 +5846,8 @@ namespace ts {
watchDirectory?: WatchDirectoryKind;
fallbackPolling?: PollingWatchKind;
synchronousWatchDirectory?: boolean;
excludeDirectories?: string[];
excludeFiles?: string[];
[option: string]: CompilerOptionsValue | undefined;
}
@@ -6016,6 +6018,7 @@ namespace ts {
affectsSemanticDiagnostics?: true; // true if option affects semantic diagnostics
affectsEmit?: true; // true if the options affects emit
transpileOptionValue?: boolean | undefined; // If set this means that the option should be set to this value when transpiling
extraValidation?: (value: CompilerOptionsValue) => [DiagnosticMessage, ...string[]] | undefined; // Additional validation to be performed for the value to be valid
}
/* @internal */
+2 -2
View File
@@ -270,10 +270,10 @@ namespace ts {
writeLog: (s: string) => void;
}
export function createWatchFactory<Y = undefined>(host: { trace?(s: string): void; }, options: { extendedDiagnostics?: boolean; diagnostics?: boolean; }) {
export function createWatchFactory<Y = undefined>(host: WatchFactoryHost & { trace?(s: string): void; }, options: { extendedDiagnostics?: boolean; diagnostics?: boolean; }) {
const watchLogLevel = host.trace ? options.extendedDiagnostics ? WatchLogLevel.Verbose : options.diagnostics ? WatchLogLevel.TriggerOnly : WatchLogLevel.None : WatchLogLevel.None;
const writeLog: (s: string) => void = watchLogLevel !== WatchLogLevel.None ? (s => host.trace!(s)) : noop;
const result = getWatchFactory<WatchType, Y>(watchLogLevel, writeLog) as WatchFactory<WatchType, Y>;
const result = getWatchFactory<WatchType, Y>(host, watchLogLevel, writeLog) as WatchFactory<WatchType, Y>;
result.writeLog = writeLog;
return result;
}
+18 -8
View File
@@ -283,13 +283,13 @@ namespace ts {
newLine = updateNewLine();
}
const { watchFile, watchFilePath, watchDirectory, writeLog } = createWatchFactory<string>(host, compilerOptions);
const { watchFile, watchDirectory, writeLog } = createWatchFactory(host, compilerOptions);
const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
writeLog(`Current directory: ${currentDirectory} CaseSensitiveFileNames: ${useCaseSensitiveFileNames}`);
let configFileWatcher: FileWatcher | undefined;
if (configFileName) {
configFileWatcher = watchFile(host, configFileName, scheduleProgramReload, PollingInterval.High, watchOptions, WatchType.ConfigFile);
configFileWatcher = watchFile(configFileName, scheduleProgramReload, PollingInterval.High, watchOptions, WatchType.ConfigFile);
}
const compilerHost = createCompilerHostFromProgramHost(host, () => compilerOptions, directoryStructureHost) as CompilerHost & ResolutionCacheHost;
@@ -305,8 +305,8 @@ namespace ts {
compilerHost.toPath = toPath;
compilerHost.getCompilationSettings = () => compilerOptions;
compilerHost.useSourceOfProjectReferenceRedirect = maybeBind(host, host.useSourceOfProjectReferenceRedirect);
compilerHost.watchDirectoryOfFailedLookupLocation = (dir, cb, flags) => watchDirectory(host, dir, cb, flags, watchOptions, WatchType.FailedLookupLocations);
compilerHost.watchTypeRootsDirectory = (dir, cb, flags) => watchDirectory(host, dir, cb, flags, watchOptions, WatchType.TypeRoots);
compilerHost.watchDirectoryOfFailedLookupLocation = (dir, cb, flags) => watchDirectory(dir, cb, flags, watchOptions, WatchType.FailedLookupLocations);
compilerHost.watchTypeRootsDirectory = (dir, cb, flags) => watchDirectory(dir, cb, flags, watchOptions, WatchType.TypeRoots);
compilerHost.getCachedDirectoryStructureHost = () => cachedDirectoryStructureHost;
compilerHost.scheduleInvalidateResolutionsOfFailedLookupLocations = scheduleInvalidateResolutionsOfFailedLookupLocations;
compilerHost.onInvalidatedResolution = scheduleProgramUpdate;
@@ -488,7 +488,7 @@ namespace ts {
(hostSourceFile as FilePresentOnHost).sourceFile = sourceFile;
hostSourceFile.version = sourceFile.version;
if (!hostSourceFile.fileWatcher) {
hostSourceFile.fileWatcher = watchFilePath(host, fileName, onSourceFileChange, PollingInterval.Low, watchOptions, path, WatchType.SourceFile);
hostSourceFile.fileWatcher = watchFilePath(path, fileName, onSourceFileChange, PollingInterval.Low, watchOptions, WatchType.SourceFile);
}
}
else {
@@ -501,7 +501,7 @@ namespace ts {
}
else {
if (sourceFile) {
const fileWatcher = watchFilePath(host, fileName, onSourceFileChange, PollingInterval.Low, watchOptions, path, WatchType.SourceFile);
const fileWatcher = watchFilePath(path, fileName, onSourceFileChange, PollingInterval.Low, watchOptions, WatchType.SourceFile);
sourceFilesCache.set(path, { sourceFile, version: sourceFile.version, fileWatcher });
}
else {
@@ -675,6 +675,17 @@ namespace ts {
hasChangedConfigFileParsingErrors = true;
}
function watchFilePath(
path: Path,
file: string,
callback: (fileName: string, eventKind: FileWatcherEventKind, filePath: Path) => void,
pollingInterval: PollingInterval,
options: WatchOptions | undefined,
watchType: WatchType
): FileWatcher {
return watchFile(file, (fileName, eventKind) => callback(fileName, eventKind, path), pollingInterval, options, watchType);
}
function onSourceFileChange(fileName: string, eventKind: FileWatcherEventKind, path: Path) {
updateCachedSystemWithFile(fileName, path, eventKind);
@@ -696,7 +707,7 @@ namespace ts {
}
function watchMissingFilePath(missingFilePath: Path) {
return watchFilePath(host, missingFilePath, onMissingFileChange, PollingInterval.Medium, watchOptions, missingFilePath, WatchType.MissingFile);
return watchFilePath(missingFilePath, missingFilePath, onMissingFileChange, PollingInterval.Medium, watchOptions, WatchType.MissingFile);
}
function onMissingFileChange(fileName: string, eventKind: FileWatcherEventKind, missingFilePath: Path) {
@@ -729,7 +740,6 @@ namespace ts {
function watchWildcardDirectory(directory: string, flags: WatchDirectoryFlags) {
return watchDirectory(
host,
directory,
fileOrDirectory => {
Debug.assert(!!configFileName);
+123 -101
View File
@@ -421,117 +421,143 @@ namespace ts {
Verbose
}
export interface WatchFileHost {
export interface WatchFactoryHost {
watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: WatchOptions): FileWatcher;
}
export interface WatchDirectoryHost {
watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: WatchOptions): FileWatcher;
}
export type WatchFile<X, Y> = (host: WatchFileHost, file: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined, 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, options: WatchOptions | undefined, path: Path, detailInfo1: X, detailInfo2?: Y) => FileWatcher;
export type WatchDirectory<X, Y> = (host: WatchDirectoryHost, directory: string, callback: DirectoryWatcherCallback, flags: WatchDirectoryFlags, options: WatchOptions | undefined, detailInfo1: X, detailInfo2?: Y) => FileWatcher;
export interface WatchFactory<X, Y> {
watchFile: WatchFile<X, Y>;
watchFilePath: WatchFilePath<X, Y>;
watchDirectory: WatchDirectory<X, Y>;
getCurrentDirectory?(): string;
useCaseSensitiveFileNames: boolean | (() => boolean);
}
export function getWatchFactory<X, Y = undefined>(watchLogLevel: WatchLogLevel, log: (s: string) => void, getDetailWatchInfo?: GetDetailWatchInfo<X, Y>): WatchFactory<X, Y> {
return getWatchFactoryWith(watchLogLevel, log, getDetailWatchInfo, watchFile, watchDirectory);
export interface WatchFactory<X, Y = undefined> {
watchFile: (file: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined, detailInfo1: X, detailInfo2?: Y) => FileWatcher;
watchDirectory: (directory: string, callback: DirectoryWatcherCallback, flags: WatchDirectoryFlags, options: WatchOptions | undefined, detailInfo1: X, detailInfo2?: Y) => FileWatcher;
}
function getWatchFactoryWith<X, Y = undefined>(
watchLogLevel: WatchLogLevel,
log: (s: string) => void,
getDetailWatchInfo: GetDetailWatchInfo<X, Y> | undefined,
watchFile: (host: WatchFileHost, file: string, callback: FileWatcherCallback, watchPriority: PollingInterval, options: WatchOptions | undefined) => FileWatcher,
watchDirectory: (host: WatchDirectoryHost, directory: string, callback: DirectoryWatcherCallback, flags: WatchDirectoryFlags, options: WatchOptions | undefined) => FileWatcher
): WatchFactory<X, Y> {
const createFileWatcher: CreateFileWatcher<WatchFileHost, PollingInterval, FileWatcherEventKind, never, 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, never, X, Y> = getCreateFileWatcher(watchLogLevel, watchDirectory);
if (watchLogLevel === WatchLogLevel.Verbose && sysLog === noop) {
setSysLog(s => log(s));
}
return {
watchFile: (host, file, callback, pollingInterval, options, detailInfo1, detailInfo2) =>
createFileWatcher(host, file, callback, pollingInterval, options, /*passThrough*/ undefined, detailInfo1, detailInfo2, watchFile, log, "FileWatcher", getDetailWatchInfo),
watchFilePath: (host, file, callback, pollingInterval, options, path, detailInfo1, detailInfo2) =>
createFilePathWatcher(host, file, callback, pollingInterval, options, path, detailInfo1, detailInfo2, watchFile, log, "FileWatcher", getDetailWatchInfo),
watchDirectory: (host, directory, callback, flags, options, detailInfo1, detailInfo2) =>
createDirectoryWatcher(host, directory, callback, flags, options, /*passThrough*/ undefined, detailInfo1, detailInfo2, watchDirectory, log, "DirectoryWatcher", getDetailWatchInfo)
};
}
function watchFile(host: WatchFileHost, file: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined): FileWatcher {
return host.watchFile(file, callback, pollingInterval, options);
}
function watchFilePath(host: WatchFileHost, file: string, callback: FilePathWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined, path: Path): FileWatcher {
return watchFile(host, file, (fileName, eventKind) => callback(fileName, eventKind, path), pollingInterval, options);
}
function watchDirectory(host: WatchDirectoryHost, directory: string, callback: DirectoryWatcherCallback, flags: WatchDirectoryFlags, options: WatchOptions | undefined): FileWatcher {
return host.watchDirectory(directory, callback, (flags & WatchDirectoryFlags.Recursive) !== 0, options);
}
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, options: WatchOptions | undefined, passThrough?: V, detailInfo1?: undefined, detailInfo2?: undefined) => FileWatcher;
export type GetDetailWatchInfo<X, Y> = (detailInfo1: X, detailInfo2: Y | undefined) => string;
export function getWatchFactory<X, Y = undefined>(host: WatchFactoryHost, watchLogLevel: WatchLogLevel, log: (s: string) => void, getDetailWatchInfo?: GetDetailWatchInfo<X, Y>): WatchFactory<X, Y> {
setSysLog(watchLogLevel === WatchLogLevel.Verbose ? log : noop);
const plainInvokeFactory: WatchFactory<X, Y> = {
watchFile: (file, callback, pollingInterval, options) => host.watchFile(file, callback, pollingInterval, options),
watchDirectory: (directory, callback, flags, options) => host.watchDirectory(directory, callback, (flags & WatchDirectoryFlags.Recursive) !== 0, options),
};
const triggerInvokingFactory: WatchFactory<X, Y> | undefined = watchLogLevel !== WatchLogLevel.None ?
{
watchFile: createTriggerLoggingAddWatch("watchFile"),
watchDirectory: createTriggerLoggingAddWatch("watchDirectory")
} :
undefined;
const factory = watchLogLevel === WatchLogLevel.Verbose ?
{
watchFile: createFileWatcherWithLogging,
watchDirectory: createDirectoryWatcherWithLogging
} :
triggerInvokingFactory || plainInvokeFactory;
const excludeWatcherFactory = watchLogLevel === WatchLogLevel.Verbose ?
createExcludeWatcherWithLogging :
returnNoopFileWatcher;
type CreateFileWatcher<H, T, U, V, X, Y> = (host: H, file: string, cb: WatchCallback<U, V>, flags: T, options: WatchOptions | undefined, 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 addWatch === <any>watchDirectory ? createDirectoryWatcherWithLogging : createFileWatcherWithLogging;
return {
watchFile: createExcludeHandlingAddWatch("watchFile"),
watchDirectory: createExcludeHandlingAddWatch("watchDirectory")
};
function createExcludeHandlingAddWatch<T extends keyof WatchFactory<X, Y>>(key: T): WatchFactory<X, Y>[T] {
return (
file: string,
cb: FileWatcherCallback | DirectoryWatcherCallback,
flags: PollingInterval | WatchDirectoryFlags,
options: WatchOptions | undefined,
detailInfo1: X,
detailInfo2?: Y
) => !matchesExclude(file, key === "watchFile" ? options?.excludeFiles : options?.excludeDirectories, useCaseSensitiveFileNames(), host.getCurrentDirectory?.() || "") ?
factory[key].call(/*thisArgs*/ undefined, file, cb, flags, options, detailInfo1, detailInfo2) :
excludeWatcherFactory(file, flags, options, detailInfo1, detailInfo2);
}
}
function createFileWatcherWithLogging<H, T, U, V, X, Y>(host: H, file: string, cb: WatchCallback<U, V>, flags: T, options: WatchOptions | undefined, 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 {
log(`${watchCaption}:: Added:: ${getWatchInfo(file, flags, options, detailInfo1, detailInfo2, getDetailWatchInfo)}`);
const watcher = createFileWatcherWithTriggerLogging(host, file, cb, flags, options, passThrough, detailInfo1, detailInfo2, addWatch, log, watchCaption, getDetailWatchInfo);
return {
close: () => {
log(`${watchCaption}:: Close:: ${getWatchInfo(file, flags, options, detailInfo1, detailInfo2, getDetailWatchInfo)}`);
watcher.close();
}
};
}
function useCaseSensitiveFileNames() {
return typeof host.useCaseSensitiveFileNames === "boolean" ?
host.useCaseSensitiveFileNames :
host.useCaseSensitiveFileNames();
}
function createDirectoryWatcherWithLogging<H, T, U, V, X, Y>(host: H, file: string, cb: WatchCallback<U, V>, flags: T, options: WatchOptions | undefined, 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 {
const watchInfo = `${watchCaption}:: Added:: ${getWatchInfo(file, flags, options, detailInfo1, detailInfo2, getDetailWatchInfo)}`;
log(watchInfo);
const start = timestamp();
const watcher = createFileWatcherWithTriggerLogging(host, file, cb, flags, options, passThrough, detailInfo1, detailInfo2, addWatch, log, watchCaption, getDetailWatchInfo);
const elapsed = timestamp() - start;
log(`Elapsed:: ${elapsed}ms ${watchInfo}`);
return {
close: () => {
const watchInfo = `${watchCaption}:: Close:: ${getWatchInfo(file, flags, options, detailInfo1, detailInfo2, getDetailWatchInfo)}`;
log(watchInfo);
const start = timestamp();
watcher.close();
const elapsed = timestamp() - start;
log(`Elapsed:: ${elapsed}ms ${watchInfo}`);
}
};
}
function createExcludeWatcherWithLogging(
file: string,
flags: PollingInterval | WatchDirectoryFlags,
options: WatchOptions | undefined,
detailInfo1: X,
detailInfo2?: Y
) {
log(`ExcludeWatcher:: Added:: ${getWatchInfo(file, flags, options, detailInfo1, detailInfo2, getDetailWatchInfo)}`);
return {
close: () => log(`ExcludeWatcher:: Close:: ${getWatchInfo(file, flags, options, detailInfo1, detailInfo2, getDetailWatchInfo)}`)
};
}
function createFileWatcherWithTriggerLogging<H, T, U, V, X, Y>(host: H, file: string, cb: WatchCallback<U, V>, flags: T, options: WatchOptions | undefined, 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 {
return addWatch(host, file, (fileName, cbOptional) => {
const triggerredInfo = `${watchCaption}:: Triggered with ${fileName} ${cbOptional !== undefined ? cbOptional : ""}:: ${getWatchInfo(file, flags, options, detailInfo1, detailInfo2, getDetailWatchInfo)}`;
log(triggerredInfo);
function createFileWatcherWithLogging(
file: string,
cb: FileWatcherCallback,
flags: PollingInterval,
options: WatchOptions | undefined,
detailInfo1: X,
detailInfo2?: Y
): FileWatcher {
log(`FileWatcher:: Added:: ${getWatchInfo(file, flags, options, detailInfo1, detailInfo2, getDetailWatchInfo)}`);
const watcher = triggerInvokingFactory!.watchFile(file, cb, flags, options, detailInfo1, detailInfo2);
return {
close: () => {
log(`FileWatcher:: Close:: ${getWatchInfo(file, flags, options, detailInfo1, detailInfo2, getDetailWatchInfo)}`);
watcher.close();
}
};
}
function createDirectoryWatcherWithLogging(
file: string,
cb: DirectoryWatcherCallback,
flags: WatchDirectoryFlags,
options: WatchOptions | undefined,
detailInfo1: X,
detailInfo2?: Y
): FileWatcher {
const watchInfo = `DirectoryWatcher:: Added:: ${getWatchInfo(file, flags, options, detailInfo1, detailInfo2, getDetailWatchInfo)}`;
log(watchInfo);
const start = timestamp();
cb(fileName, cbOptional, passThrough);
const watcher = triggerInvokingFactory!.watchDirectory(file, cb, flags, options, detailInfo1, detailInfo2);
const elapsed = timestamp() - start;
log(`Elapsed:: ${elapsed}ms ${triggerredInfo}`);
}, flags, options);
log(`Elapsed:: ${elapsed}ms ${watchInfo}`);
return {
close: () => {
const watchInfo = `DirectoryWatcher:: Close:: ${getWatchInfo(file, flags, options, detailInfo1, detailInfo2, getDetailWatchInfo)}`;
log(watchInfo);
const start = timestamp();
watcher.close();
const elapsed = timestamp() - start;
log(`Elapsed:: ${elapsed}ms ${watchInfo}`);
}
};
}
function createTriggerLoggingAddWatch<T extends keyof WatchFactory<X, Y>>(key: T): WatchFactory<X, Y>[T] {
return (
file: string,
cb: FileWatcherCallback | DirectoryWatcherCallback,
flags: PollingInterval | WatchDirectoryFlags,
options: WatchOptions | undefined,
detailInfo1: X,
detailInfo2?: Y
) => plainInvokeFactory[key].call(/*thisArgs*/ undefined, file, (...args: any[]) => {
const triggerredInfo = `${key === "watchFile" ? "FileWatcher" : "DirectoryWatcher"}:: Triggered with ${args[0]} ${args[1] !== undefined ? args[1] : ""}:: ${getWatchInfo(file, flags, options, detailInfo1, detailInfo2, getDetailWatchInfo)}`;
log(triggerredInfo);
const start = timestamp();
cb.call(/*thisArg*/ undefined, ...args);
const elapsed = timestamp() - start;
log(`Elapsed:: ${elapsed}ms ${triggerredInfo}`);
}, flags, options, detailInfo1, detailInfo2);
}
function getWatchInfo<T>(file: string, flags: T, options: WatchOptions | undefined, detailInfo1: X, detailInfo2: Y | undefined, getDetailWatchInfo: GetDetailWatchInfo<X, Y> | undefined) {
return `WatchInfo: ${file} ${flags} ${JSON.stringify(options)} ${getDetailWatchInfo ? getDetailWatchInfo(detailInfo1, detailInfo2) : detailInfo2 === undefined ? detailInfo1 : `${detailInfo1} ${detailInfo2}`}`;
}
}
export function getFallbackOptions(options: WatchOptions | undefined): WatchOptions {
@@ -543,10 +569,6 @@ namespace ts {
};
}
function getWatchInfo<T, X, Y>(file: string, flags: T, options: WatchOptions | undefined, detailInfo1: X, detailInfo2: Y | undefined, getDetailWatchInfo: GetDetailWatchInfo<X, Y> | undefined) {
return `WatchInfo: ${file} ${flags} ${JSON.stringify(options)} ${getDetailWatchInfo ? getDetailWatchInfo(detailInfo1, detailInfo2) : detailInfo2 === undefined ? detailInfo1 : `${detailInfo1} ${detailInfo2}`}`;
}
export function closeFileWatcherOf<T extends { watcher: FileWatcher; }>(objWithWatcher: T) {
objWithWatcher.watcher.close();
}