mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
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:
@@ -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
@@ -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] || "";
|
||||
|
||||
@@ -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[]) {
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user