diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index b41d76acd27ec..51cf7923a1009 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -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(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, 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((valueExpression).text); + return validateValue(Number((valueExpression).text)); case SyntaxKind.PrefixUnaryExpression: if ((valueExpression).operator !== SyntaxKind.MinusToken || (valueExpression).operand.kind !== SyntaxKind.NumericLiteral) { break; // not valid JSON syntax } reportInvalidOptionValue(option && option.type !== "number"); - return -Number(((valueExpression).operand).text); + return validateValue(-Number(((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 } = 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( (valueExpression).elements, - option && (option).element); + option && (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): CompilerOptionsValue { + /*@internal*/ + export function convertJsonOption(opt: CommandLineOption, value: any, basePath: string, errors: Push): 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(opt, 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(opt: CommandLineOption, value: T, errors: Push): 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) { 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, allowTrailingRecursion: boolean, jsonSourceFile: TsConfigSourceFile | undefined, specKey: string): readonly string[] { + function validateSpecs(specs: readonly string[], errors: Push, 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]; } } diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index efa51678f4da0..231e012c510a2 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -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(); 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( - 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] || ""; diff --git a/src/compiler/tsbuildPublic.ts b/src/compiler/tsbuildPublic.ts index 8d1c59fb3fd80..f74b016093a1e 100644 --- a/src/compiler/tsbuildPublic.ts +++ b/src/compiler/tsbuildPublic.ts @@ -209,7 +209,7 @@ namespace ts { originalGetSourceFile: CompilerHost["getSourceFile"]; } - interface SolutionBuilderState { + interface SolutionBuilderState extends WatchFactory { readonly host: SolutionBuilderHost; readonly hostWithWatch: SolutionBuilderWithWatchHost; readonly currentDirectory: string; @@ -256,9 +256,6 @@ namespace ts { timerToBuildInvalidatedProject: any; reportFileChangeDetected: boolean; - watchFile: WatchFile; - watchFilePath: WatchFilePath; - watchDirectory: WatchDirectory; writeLog: (s: string) => void; } @@ -282,7 +279,7 @@ namespace ts { loadWithLocalCache(Debug.checkEachDefined(moduleNames), containingFile, redirectedReference, loader); } - const { watchFile, watchFilePath, watchDirectory, writeLog } = createWatchFactory(hostWithWatch, options); + const { watchFile, watchDirectory, writeLog } = createWatchFactory(hostWithWatch, options); const state: SolutionBuilderState = { 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[]) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 625a5d8397537..0ba174ff9ea7e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -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 */ diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 422dc13ab22b4..ab7c06ee482df 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -270,10 +270,10 @@ namespace ts { writeLog: (s: string) => void; } - export function createWatchFactory(host: { trace?(s: string): void; }, options: { extendedDiagnostics?: boolean; diagnostics?: boolean; }) { + export function createWatchFactory(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(watchLogLevel, writeLog) as WatchFactory; + const result = getWatchFactory(host, watchLogLevel, writeLog) as WatchFactory; result.writeLog = writeLog; return result; } diff --git a/src/compiler/watchPublic.ts b/src/compiler/watchPublic.ts index e56fea371993a..9c4349d711ebd 100644 --- a/src/compiler/watchPublic.ts +++ b/src/compiler/watchPublic.ts @@ -283,13 +283,13 @@ namespace ts { newLine = updateNewLine(); } - const { watchFile, watchFilePath, watchDirectory, writeLog } = createWatchFactory(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); diff --git a/src/compiler/watchUtilities.ts b/src/compiler/watchUtilities.ts index 6d4f02c1f380f..c088e2876d7fe 100644 --- a/src/compiler/watchUtilities.ts +++ b/src/compiler/watchUtilities.ts @@ -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 = (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 = (host: WatchFileHost, file: string, callback: FilePathWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined, path: Path, detailInfo1: X, detailInfo2?: Y) => FileWatcher; - export type WatchDirectory = (host: WatchDirectoryHost, directory: string, callback: DirectoryWatcherCallback, flags: WatchDirectoryFlags, options: WatchOptions | undefined, detailInfo1: X, detailInfo2?: Y) => FileWatcher; - - export interface WatchFactory { - watchFile: WatchFile; - watchFilePath: WatchFilePath; - watchDirectory: WatchDirectory; + getCurrentDirectory?(): string; + useCaseSensitiveFileNames: boolean | (() => boolean); } - export function getWatchFactory(watchLogLevel: WatchLogLevel, log: (s: string) => void, getDetailWatchInfo?: GetDetailWatchInfo): WatchFactory { - return getWatchFactoryWith(watchLogLevel, log, getDetailWatchInfo, watchFile, watchDirectory); + export interface WatchFactory { + 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( - watchLogLevel: WatchLogLevel, - log: (s: string) => void, - getDetailWatchInfo: GetDetailWatchInfo | 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 { - const createFileWatcher: CreateFileWatcher = getCreateFileWatcher(watchLogLevel, watchFile); - const createFilePathWatcher: CreateFileWatcher = watchLogLevel === WatchLogLevel.None ? watchFilePath : createFileWatcher; - const createDirectoryWatcher: CreateFileWatcher = 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) + export type GetDetailWatchInfo = (detailInfo1: X, detailInfo2: Y | undefined) => string; + export function getWatchFactory(host: WatchFactoryHost, watchLogLevel: WatchLogLevel, log: (s: string) => void, getDetailWatchInfo?: GetDetailWatchInfo): WatchFactory { + setSysLog(watchLogLevel === WatchLogLevel.Verbose ? log : noop); + const plainInvokeFactory: WatchFactory = { + 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), }; - } - - 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); - } + const triggerInvokingFactory: WatchFactory | 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; - function watchDirectory(host: WatchDirectoryHost, directory: string, callback: DirectoryWatcherCallback, flags: WatchDirectoryFlags, options: WatchOptions | undefined): FileWatcher { - return host.watchDirectory(directory, callback, (flags & WatchDirectoryFlags.Recursive) !== 0, options); - } + return { + watchFile: createExcludeHandlingAddWatch("watchFile"), + watchDirectory: createExcludeHandlingAddWatch("watchDirectory") + }; - type WatchCallback = (fileName: string, cbOptional?: T, passThrough?: U) => void; - type AddWatch = (host: H, file: string, cb: WatchCallback, flags: T, options: WatchOptions | undefined, passThrough?: V, detailInfo1?: undefined, detailInfo2?: undefined) => FileWatcher; - export type GetDetailWatchInfo = (detailInfo1: X, detailInfo2: Y | undefined) => string; + function createExcludeHandlingAddWatch>(key: T): WatchFactory[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 useCaseSensitiveFileNames() { + return typeof host.useCaseSensitiveFileNames === "boolean" ? + host.useCaseSensitiveFileNames : + host.useCaseSensitiveFileNames(); + } + + 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)}`) + }; + } - type CreateFileWatcher = (host: H, file: string, cb: WatchCallback, flags: T, options: WatchOptions | undefined, passThrough: V | undefined, detailInfo1: X | undefined, detailInfo2: Y | undefined, addWatch: AddWatch, log: (s: string) => void, watchCaption: string, getDetailWatchInfo: GetDetailWatchInfo | undefined) => FileWatcher; - function getCreateFileWatcher(watchLogLevel: WatchLogLevel, addWatch: AddWatch): CreateFileWatcher { - switch (watchLogLevel) { - case WatchLogLevel.None: - return addWatch; - case WatchLogLevel.TriggerOnly: - return createFileWatcherWithTriggerLogging; - case WatchLogLevel.Verbose: - return addWatch === watchDirectory ? createDirectoryWatcherWithLogging : createFileWatcherWithLogging; + 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 createFileWatcherWithLogging(host: H, file: string, cb: WatchCallback, flags: T, options: WatchOptions | undefined, passThrough: V | undefined, detailInfo1: X | undefined, detailInfo2: Y | undefined, addWatch: AddWatch, log: (s: string) => void, watchCaption: string, getDetailWatchInfo: GetDetailWatchInfo | 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 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(); + const watcher = triggerInvokingFactory!.watchDirectory(file, cb, flags, options, detailInfo1, detailInfo2); + const elapsed = timestamp() - start; + 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 createDirectoryWatcherWithLogging(host: H, file: string, cb: WatchCallback, flags: T, options: WatchOptions | undefined, passThrough: V | undefined, detailInfo1: X | undefined, detailInfo2: Y | undefined, addWatch: AddWatch, log: (s: string) => void, watchCaption: string, getDetailWatchInfo: GetDetailWatchInfo | 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); + function createTriggerLoggingAddWatch>(key: T): WatchFactory[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(); - watcher.close(); + cb.call(/*thisArg*/ undefined, ...args); const elapsed = timestamp() - start; - log(`Elapsed:: ${elapsed}ms ${watchInfo}`); - } - }; - } + log(`Elapsed:: ${elapsed}ms ${triggerredInfo}`); + }, flags, options, detailInfo1, detailInfo2); + } - function createFileWatcherWithTriggerLogging(host: H, file: string, cb: WatchCallback, flags: T, options: WatchOptions | undefined, passThrough: V | undefined, detailInfo1: X | undefined, detailInfo2: Y | undefined, addWatch: AddWatch, log: (s: string) => void, watchCaption: string, getDetailWatchInfo: GetDetailWatchInfo | 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); - const start = timestamp(); - cb(fileName, cbOptional, passThrough); - const elapsed = timestamp() - start; - log(`Elapsed:: ${elapsed}ms ${triggerredInfo}`); - }, flags, options); + function getWatchInfo(file: string, flags: T, options: WatchOptions | undefined, detailInfo1: X, detailInfo2: Y | undefined, getDetailWatchInfo: GetDetailWatchInfo | 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(file: string, flags: T, options: WatchOptions | undefined, detailInfo1: X, detailInfo2: Y | undefined, getDetailWatchInfo: GetDetailWatchInfo | undefined) { - return `WatchInfo: ${file} ${flags} ${JSON.stringify(options)} ${getDetailWatchInfo ? getDetailWatchInfo(detailInfo1, detailInfo2) : detailInfo2 === undefined ? detailInfo1 : `${detailInfo1} ${detailInfo2}`}`; - } - export function closeFileWatcherOf(objWithWatcher: T) { objWithWatcher.watcher.close(); } diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index e02d01a75285d..a702be25549a1 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -251,7 +251,7 @@ interface Array { length: number; [n: number]: T; }` else { recursiveOrExpectedDetails = recursiveOrEachDirectoryWatchCount as boolean; checkMap( - `fsWatches{recursive ? " recursive" : ""}`, + `fsWatches${recursiveOrExpectedDetails ? " recursive" : ""}`, recursiveOrExpectedDetails ? host.fsWatchesRecursive : host.fsWatches, expectedDirectories, [expectedDetails, ({ directoryName, fallbackPollingInterval, fallbackOptions }) => ({ directoryName, fallbackPollingInterval, fallbackOptions })] @@ -433,6 +433,7 @@ interface Array { length: number; [n: number]: T; }` fsWatch: this.fsWatch.bind(this), fileExists: this.fileExists.bind(this), useCaseSensitiveFileNames: this.useCaseSensitiveFileNames, + getCurrentDirectory: this.getCurrentDirectory.bind(this), fsSupportsRecursiveFsWatch: tscWatchDirectory ? false : !runWithoutRecursiveWatches, directoryExists: this.directoryExists.bind(this), getAccessibleSortedChildDirectories: path => this.getDirectories(path), diff --git a/src/jsTyping/types.ts b/src/jsTyping/types.ts index 34f69d0d624df..26d2c69795d96 100644 --- a/src/jsTyping/types.ts +++ b/src/jsTyping/types.ts @@ -83,8 +83,9 @@ declare namespace ts.server { useCaseSensitiveFileNames: boolean; writeFile(path: string, content: string): void; createDirectory(path: string): void; - watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: CompilerOptions): FileWatcher; - watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: CompilerOptions): FileWatcher; + getCurrentDirectory?(): string; + watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: WatchOptions): FileWatcher; + watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: WatchOptions): FileWatcher; } export interface SetTypings extends ProjectResponse { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 2747a6f03f6b8..a2fe4bd897296 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -11,6 +11,7 @@ namespace ts.server { export const ProjectLanguageServiceStateEvent = "projectLanguageServiceState"; export const ProjectInfoTelemetryEvent = "projectInfo"; export const OpenFileInfoTelemetryEvent = "openFileInfo"; + const ensureProjectForOpenFileSchedule = "*ensureProjectForOpenFiles*"; export interface ProjectsUpdatedInBackgroundEvent { eventName: typeof ProjectsUpdatedInBackgroundEvent; @@ -251,16 +252,18 @@ namespace ts.server { return protocolOptions; } - export function convertWatchOptions(protocolOptions: protocol.ExternalProjectCompilerOptions): WatchOptions | undefined { - let result: WatchOptions | undefined; - watchOptionsConverters.forEach((mappedValues, id) => { - const propertyValue = protocolOptions[id]; + export function convertWatchOptions(protocolOptions: protocol.ExternalProjectCompilerOptions, currentDirectory?: string): WatchOptionsAndErrors | undefined { + let watchOptions: WatchOptions | undefined; + let errors: Diagnostic[] | undefined; + optionsForWatch.forEach(option => { + const propertyValue = protocolOptions[option.name]; if (propertyValue === undefined) return; - (result || (result = {}))[id] = isString(propertyValue) ? - mappedValues.get(propertyValue.toLowerCase()) : - propertyValue; + const mappedValues = watchOptionsConverters.get(option.name); + (watchOptions || (watchOptions = {}))[option.name] = mappedValues ? + isString(propertyValue) ? mappedValues.get(propertyValue.toLowerCase()) : propertyValue : + convertJsonOption(option, propertyValue, currentDirectory || "", errors || (errors = [])); }); - return result; + return watchOptions && { watchOptions, errors }; } export function convertTypeAcquisition(protocolOptions: protocol.InferredProjectCompilerOptions): TypeAcquisition | undefined { @@ -633,6 +636,11 @@ namespace ts.server { changes: Iterator; } + export interface WatchOptionsAndErrors { + watchOptions: WatchOptions; + errors: Diagnostic[] | undefined; + } + export class ProjectService { /*@internal*/ @@ -691,8 +699,8 @@ namespace ts.server { private compilerOptionsForInferredProjects: CompilerOptions | undefined; private compilerOptionsForInferredProjectsPerProjectRoot = new Map(); - private watchOptionsForInferredProjects: WatchOptions | undefined; - private watchOptionsForInferredProjectsPerProjectRoot = new Map(); + private watchOptionsForInferredProjects: WatchOptionsAndErrors | undefined; + private watchOptionsForInferredProjectsPerProjectRoot = new Map(); private typeAcquisitionForInferredProjects: TypeAcquisition | undefined; private typeAcquisitionForInferredProjectsPerProjectRoot = new Map(); /** @@ -819,10 +827,9 @@ namespace ts.server { this.watchFactory = this.serverMode !== LanguageServiceMode.Semantic ? { watchFile: returnNoopFileWatcher, - watchFilePath: returnNoopFileWatcher, watchDirectory: returnNoopFileWatcher, } : - getWatchFactory(watchLogLevel, log, getDetailWatchInfo); + getWatchFactory(this.host, watchLogLevel, log, getDetailWatchInfo); } toPath(fileName: string) { @@ -924,7 +931,7 @@ namespace ts.server { /*@internal*/ delayEnsureProjectForOpenFiles() { this.pendingEnsureProjectForOpenFiles = true; - this.throttledOperations.schedule("*ensureProjectForOpenFiles*", /*delay*/ 2500, () => { + this.throttledOperations.schedule(ensureProjectForOpenFileSchedule, /*delay*/ 2500, () => { if (this.pendingProjectUpdates.size !== 0) { this.delayEnsureProjectForOpenFiles(); } @@ -1039,7 +1046,7 @@ namespace ts.server { Debug.assert(projectRootPath === undefined || this.useInferredProjectPerProjectRoot, "Setting compiler options per project root path is only supported when useInferredProjectPerProjectRoot is enabled"); const compilerOptions = convertCompilerOptions(projectCompilerOptions); - const watchOptions = convertWatchOptions(projectCompilerOptions); + const watchOptions = convertWatchOptions(projectCompilerOptions, projectRootPath); const typeAcquisition = convertTypeAcquisition(projectCompilerOptions); // always set 'allowNonTsExtensions' for inferred projects since user cannot configure it from the outside @@ -1070,8 +1077,9 @@ namespace ts.server { project.projectRootPath === canonicalProjectRootPath : !project.projectRootPath || !this.compilerOptionsForInferredProjectsPerProjectRoot.has(project.projectRootPath)) { project.setCompilerOptions(compilerOptions); - project.setWatchOptions(watchOptions); project.setTypeAcquisition(typeAcquisition); + project.setWatchOptions(watchOptions?.watchOptions); + project.setProjectErrors(watchOptions?.errors); project.compileOnSaveEnabled = compilerOptions.compileOnSave!; project.markAsDirty(); this.delayUpdateProjectGraph(project); @@ -1173,26 +1181,20 @@ namespace ts.server { return this.hostConfiguration.preferences; } - private onSourceFileChanged(fileName: string, eventKind: FileWatcherEventKind, path: Path) { - const info = this.getScriptInfoForPath(path); - if (!info) { - this.logger.msg(`Error: got watch notification for unknown file: ${fileName}`); + private onSourceFileChanged(info: ScriptInfo, eventKind: FileWatcherEventKind) { + if (info.containingProjects) { + info.containingProjects.forEach(project => project.resolutionCache.removeResolutionsFromProjectReferenceRedirects(info.path)); } - else { - if (info.containingProjects) { - info.containingProjects.forEach(project => project.resolutionCache.removeResolutionsFromProjectReferenceRedirects(info.path)); - } - if (eventKind === FileWatcherEventKind.Deleted) { - // File was deleted - this.handleDeletedFile(info); - } - else if (!info.isScriptOpen()) { - // file has been changed which might affect the set of referenced files in projects that include - // this file and set of inferred projects - info.delayReloadNonMixedContentFile(); - this.delayUpdateProjectGraphs(info.containingProjects, /*clearSourceMapperCache*/ false); - this.handleSourceMapProjects(info); - } + if (eventKind === FileWatcherEventKind.Deleted) { + // File was deleted + this.handleDeletedFile(info); + } + else if (!info.isScriptOpen()) { + // file has been changed which might affect the set of referenced files in projects that include + // this file and set of inferred projects + info.delayReloadNonMixedContentFile(); + this.delayUpdateProjectGraphs(info.containingProjects, /*clearSourceMapperCache*/ false); + this.handleSourceMapProjects(info); } } @@ -1259,7 +1261,6 @@ namespace ts.server { watchWildcardDirectory(directory: Path, flags: WatchDirectoryFlags, project: ConfiguredProject) { const watchOptions = this.getWatchOptions(project); return this.watchFactory.watchDirectory( - this.host, directory, fileOrDirectory => { const fileOrDirectoryPath = this.toPath(fileOrDirectory); @@ -1683,7 +1684,6 @@ namespace ts.server { configFileExistenceInfo.configFileWatcherForRootOfInferredProject = canWatchDirectory(getDirectoryPath(canonicalConfigFilePath) as Path) ? this.watchFactory.watchFile( - this.host, configFileName, (_filename, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind), PollingInterval.High, @@ -1968,7 +1968,7 @@ namespace ts.server { private createExternalProject(projectFileName: string, files: protocol.ExternalFile[], options: protocol.ExternalProjectCompilerOptions, typeAcquisition: TypeAcquisition, excludedFiles: NormalizedPath[]) { const compilerOptions = convertCompilerOptions(options); - const watchOptions = convertWatchOptions(options); + const watchOptionsAndErrors = convertWatchOptions(options, getDirectoryPath(normalizeSlashes(projectFileName))); const project = new ExternalProject( projectFileName, this, @@ -1978,8 +1978,9 @@ namespace ts.server { options.compileOnSave === undefined ? true : options.compileOnSave, /*projectFilePath*/ undefined, this.currentPluginConfigOverrides, - watchOptions + watchOptionsAndErrors?.watchOptions ); + project.setProjectErrors(watchOptionsAndErrors?.errors); project.excludedFiles = excludedFiles; this.addFilesToNonInferredProject(project, files, externalFilePropertyReader, typeAcquisition); @@ -2252,9 +2253,10 @@ namespace ts.server { * Read the config file of the project again by clearing the cache and update the project graph */ /* @internal */ - reloadConfiguredProject(project: ConfiguredProject, reason: string, isInitialLoad: boolean) { + reloadConfiguredProject(project: ConfiguredProject, reason: string, isInitialLoad: boolean, clearSemanticCache: boolean) { // At this point, there is no reason to not have configFile in the host const host = project.getCachedDirectoryStructureHost(); + if (clearSemanticCache) this.clearSemanticCache(project); // Clear the cache since we are reloading the project from disk host.clearCache(); @@ -2268,6 +2270,13 @@ namespace ts.server { this.sendConfigFileDiagEvent(project, configFileName); } + /* @internal */ + private clearSemanticCache(project: Project) { + project.resolutionCache.clear(); + project.getLanguageService(/*ensureSynchronized*/ false).cleanupSemanticCache(); + project.markAsDirty(); + } + private sendConfigFileDiagEvent(project: ConfiguredProject, triggerFile: NormalizedPath) { if (!this.eventHandler || this.suppressDiagnosticEvents) { return; @@ -2354,19 +2363,21 @@ namespace ts.server { private createInferredProject(currentDirectory: string | undefined, isSingleInferredProject?: boolean, projectRootPath?: NormalizedPath): InferredProject { const compilerOptions = projectRootPath && this.compilerOptionsForInferredProjectsPerProjectRoot.get(projectRootPath) || this.compilerOptionsForInferredProjects!; // TODO: GH#18217 - let watchOptions: WatchOptions | false | undefined; + let watchOptionsAndErrors: WatchOptionsAndErrors | false | undefined; let typeAcquisition: TypeAcquisition | undefined; if (projectRootPath) { - watchOptions = this.watchOptionsForInferredProjectsPerProjectRoot.get(projectRootPath); + watchOptionsAndErrors = this.watchOptionsForInferredProjectsPerProjectRoot.get(projectRootPath); typeAcquisition = this.typeAcquisitionForInferredProjectsPerProjectRoot.get(projectRootPath); } - if (watchOptions === undefined) { - watchOptions = this.watchOptionsForInferredProjects; + if (watchOptionsAndErrors === undefined) { + watchOptionsAndErrors = this.watchOptionsForInferredProjects; } if (typeAcquisition === undefined) { typeAcquisition = this.typeAcquisitionForInferredProjects; } - const project = new InferredProject(this, this.documentRegistry, compilerOptions, watchOptions || undefined, projectRootPath, currentDirectory, this.currentPluginConfigOverrides, typeAcquisition); + watchOptionsAndErrors = watchOptionsAndErrors || undefined; + const project = new InferredProject(this, this.documentRegistry, compilerOptions, watchOptionsAndErrors?.watchOptions, projectRootPath, currentDirectory, this.currentPluginConfigOverrides, typeAcquisition); + project.setProjectErrors(watchOptionsAndErrors?.errors); if (isSingleInferredProject) { this.inferredProjects.unshift(project); } @@ -2450,13 +2461,11 @@ namespace ts.server { !startsWith(info.path, this.globalCacheLocationDirectoryPath))) { const indexOfNodeModules = info.path.indexOf("/node_modules/"); if (!this.host.getModifiedTime || indexOfNodeModules === -1) { - info.fileWatcher = this.watchFactory.watchFilePath( - this.host, + info.fileWatcher = this.watchFactory.watchFile( info.fileName, - (fileName, eventKind, path) => this.onSourceFileChanged(fileName, eventKind, path), + (_fileName, eventKind) => this.onSourceFileChanged(info, eventKind), PollingInterval.Medium, this.hostConfiguration.watchOptions, - info.path, WatchType.ClosedScriptInfo ); } @@ -2477,9 +2486,8 @@ namespace ts.server { const watchDir = dir + "/node_modules" as Path; const watcher = this.watchFactory.watchDirectory( - this.host, watchDir, - (fileOrDirectory) => { + fileOrDirectory => { const fileOrDirectoryPath = removeIgnoredPath(this.toPath(fileOrDirectory)); if (!fileOrDirectoryPath) return; @@ -2530,7 +2538,7 @@ namespace ts.server { if (mTime !== info.mTime) { const eventKind = getFileWatcherEventKind(info.mTime!, mTime); info.mTime = mTime; - this.onSourceFileChanged(info.fileName, eventKind, info.path); + this.onSourceFileChanged(info, eventKind); } } @@ -2715,7 +2723,6 @@ namespace ts.server { private addMissingSourceMapFile(mapFileName: string, declarationInfoPath: Path) { const fileWatcher = this.watchFactory.watchFile( - this.host, mapFileName, () => { const declarationInfo = this.getScriptInfoForPath(declarationInfoPath); @@ -2822,7 +2829,7 @@ namespace ts.server { } if (args.watchOptions) { - this.hostConfiguration.watchOptions = convertWatchOptions(args.watchOptions); + this.hostConfiguration.watchOptions = convertWatchOptions(args.watchOptions)?.watchOptions; this.logger.info(`Host watch options changed to ${JSON.stringify(this.hostConfiguration.watchOptions)}, it will be take effect for next watches.`); } } @@ -2851,8 +2858,28 @@ namespace ts.server { // (and would separate out below reloading of projects to be called when immediate reload is needed) // as there is no need to load contents of the files from the disk + // Reload script infos + this.filenameToScriptInfo.forEach(info => { + if (this.openFiles.has(info.path)) return; // Skip open files + if (!info.fileWatcher) return; // not watched file + // Handle as if file is changed or deleted + this.onSourceFileChanged(info, this.host.fileExists(info.fileName) ? FileWatcherEventKind.Changed : FileWatcherEventKind.Deleted); + }); + // Cancel all project updates since we will be updating them now + this.pendingProjectUpdates.forEach((_project, projectName) => { + this.throttledOperations.cancel(projectName); + this.pendingProjectUpdates.delete(projectName); + }); + this.throttledOperations.cancel(ensureProjectForOpenFileSchedule); + this.pendingEnsureProjectForOpenFiles = false; + // Reload Projects - this.reloadConfiguredProjectForFiles(this.openFiles as ESMap, /*delayReload*/ false, returnTrue, "User requested reload projects"); + this.reloadConfiguredProjectForFiles(this.openFiles as ESMap, /*clearSemanticCache*/ true, /*delayReload*/ false, returnTrue, "User requested reload projects"); + this.externalProjects.forEach(project => { + this.clearSemanticCache(project); + project.updateGraph(); + }); + this.inferredProjects.forEach(project => this.clearSemanticCache(project)); this.ensureProjectForOpenFiles(); } @@ -2860,6 +2887,7 @@ namespace ts.server { // Get open files to reload projects for this.reloadConfiguredProjectForFiles( configFileExistenceInfo.openFilesImpactedByConfigFile, + /*clearSemanticCache*/ false, /*delayReload*/ true, ignoreIfNotRootOfInferredProject ? isRootOfInferredProject => isRootOfInferredProject : // Reload open files if they are root of inferred project @@ -2876,12 +2904,12 @@ namespace ts.server { * If the there is no existing project it just opens the configured project for the config file * reloadForInfo provides a way to filter out files to reload configured project for */ - private reloadConfiguredProjectForFiles(openFiles: ESMap, delayReload: boolean, shouldReloadProjectFor: (openFileValue: T) => boolean, reason: string) { + private reloadConfiguredProjectForFiles(openFiles: ESMap, clearSemanticCache: boolean, delayReload: boolean, shouldReloadProjectFor: (openFileValue: T) => boolean, reason: string) { const updatedProjects = new Map(); const reloadChildProject = (child: ConfiguredProject) => { if (!updatedProjects.has(child.canonicalConfigFilePath)) { updatedProjects.set(child.canonicalConfigFilePath, true); - this.reloadConfiguredProject(child, reason, /*isInitialLoad*/ false); + this.reloadConfiguredProject(child, reason, /*isInitialLoad*/ false, clearSemanticCache); } }; // try to reload config file for all open files @@ -2907,11 +2935,12 @@ namespace ts.server { if (delayReload) { project.pendingReload = ConfigFileProgramReloadLevel.Full; project.pendingReloadReason = reason; + if (clearSemanticCache) this.clearSemanticCache(project); this.delayUpdateProjectGraph(project); } else { // reload from the disk - this.reloadConfiguredProject(project, reason, /*isInitialLoad*/ false); + this.reloadConfiguredProject(project, reason, /*isInitialLoad*/ false, clearSemanticCache); // If this project does not contain this file directly, reload the project till the reloaded project contains the script info directly if (!projectContainsInfoDirectly(project, info)) { const referencedProject = forEachResolvedProjectReferenceProject( @@ -3730,7 +3759,7 @@ namespace ts.server { externalProject.excludedFiles = excludedFiles; if (!tsConfigFiles) { const compilerOptions = convertCompilerOptions(proj.options); - const watchOptions = convertWatchOptions(proj.options); + const watchOptionsAndErrors = convertWatchOptions(proj.options, externalProject.getCurrentDirectory()); const lastFileExceededProgramSize = this.getFilenameForExceededTotalSizeLimitForNonTsFiles(proj.projectFileName, compilerOptions, proj.rootFiles, externalFilePropertyReader); if (lastFileExceededProgramSize) { externalProject.disableLanguageService(lastFileExceededProgramSize); @@ -3738,9 +3767,10 @@ namespace ts.server { else { externalProject.enableLanguageService(); } + externalProject.setProjectErrors(watchOptionsAndErrors?.errors); // external project already exists and not config files were added - update the project and return; // The graph update here isnt postponed since any file open operation needs all updated external projects - this.updateRootAndOptionsOfNonInferredProject(externalProject, proj.rootFiles, externalFilePropertyReader, compilerOptions, proj.typeAcquisition, proj.options.compileOnSave, watchOptions); + this.updateRootAndOptionsOfNonInferredProject(externalProject, proj.rootFiles, externalFilePropertyReader, compilerOptions, proj.typeAcquisition, proj.options.compileOnSave, watchOptionsAndErrors?.watchOptions); externalProject.updateGraph(); return; } @@ -3863,7 +3893,6 @@ namespace ts.server { if (!watchers.has(path)) { this.invalidateProjectAutoImports(path); watchers.set(path, this.watchFactory.watchFile( - this.host, path, (fileName, eventKind) => { const path = this.toPath(fileName); diff --git a/src/server/project.ts b/src/server/project.ts index 1b18be8db98f6..6e7de6af2dc5c 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -189,6 +189,8 @@ namespace ts.server { */ private projectStateVersion = 0; + protected projectErrors: Diagnostic[] | undefined; + protected isInitialLoadPending: () => boolean = returnFalse; /*@internal*/ @@ -489,7 +491,6 @@ namespace ts.server { /*@internal*/ watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags) { return this.projectService.watchFactory.watchDirectory( - this.projectService.host, directory, cb, flags, @@ -530,7 +531,6 @@ namespace ts.server { /*@internal*/ watchTypeRootsDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags) { return this.projectService.watchFactory.watchDirectory( - this.projectService.host, directory, cb, flags, @@ -586,11 +586,18 @@ namespace ts.server { * Get the errors that dont have any file name associated */ getGlobalProjectErrors(): readonly Diagnostic[] { - return emptyArray; + return filter(this.projectErrors, diagnostic => !diagnostic.file) || emptyArray; } + /** + * Get all the project errors + */ getAllProjectErrors(): readonly Diagnostic[] { - return emptyArray; + return this.projectErrors || emptyArray; + } + + setProjectErrors(projectErrors: Diagnostic[] | undefined) { + this.projectErrors = projectErrors; } getLanguageService(ensureSynchronized = true): LanguageService { @@ -771,6 +778,7 @@ namespace ts.server { this.resolutionCache = undefined!; this.cachedUnresolvedImportsPerFile = undefined!; this.directoryStructureHost = undefined!; + this.projectErrors = undefined; // Clean up file watchers waiting for missing files if (this.missingFilesMap) { @@ -1262,7 +1270,6 @@ namespace ts.server { private addMissingFileWatcher(missingFilePath: Path) { const fileWatcher = this.projectService.watchFactory.watchFile( - this.projectService.host, missingFilePath, (fileName, eventKind) => { if (isConfiguredProject(this)) { @@ -1318,7 +1325,6 @@ namespace ts.server { return { generatedFilePath: this.toPath(generatedFile), watcher: this.projectService.watchFactory.watchFile( - this.projectService.host, generatedFile, () => { this.clearSourceMapperCache(); @@ -2060,8 +2066,6 @@ namespace ts.server { /** Ref count to the project when opened from external project */ private externalProjectRefCount = 0; - private projectErrors: Diagnostic[] | undefined; - private projectReferences: readonly ProjectReference[] | undefined; /** Potential project references before the project is actually loaded (read config file) */ @@ -2131,7 +2135,6 @@ namespace ts.server { /* @internal */ createConfigFileWatcher() { this.configFileWatcher = this.projectService.watchFactory.watchFile( - this.projectService.host, this.getConfigFilePath(), (_fileName, eventKind) => this.projectService.onConfigChangedForConfiguredProject(this, eventKind), PollingInterval.High, @@ -2160,7 +2163,7 @@ namespace ts.server { this.openFileWatchTriggered.clear(); const reason = Debug.checkDefined(this.pendingReloadReason); this.pendingReloadReason = undefined; - this.projectService.reloadConfiguredProject(this, reason, isInitialLoad); + this.projectService.reloadConfiguredProject(this, reason, isInitialLoad, /*clearSemanticCache*/ false); result = true; break; default: @@ -2281,7 +2284,6 @@ namespace ts.server { } this.stopWatchingWildCards(); - this.projectErrors = undefined; this.configFileSpecs = undefined; this.openFileWatchTriggered.clear(); this.compilerHost = undefined; diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 0bc9f3ac0c4b6..54eddf818ff33 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -1509,6 +1509,8 @@ namespace ts.server.protocol { watchDirectory?: WatchDirectoryKind | ts.WatchDirectoryKind; fallbackPolling?: PollingWatchKind | ts.PollingWatchKind; synchronousWatchDirectory?: boolean; + excludeDirectories?: string[]; + excludeFiles?: string[]; [option: string]: CompilerOptionsValue | undefined; } diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index 7d924bb8de7f5..c024bdb0d6d37 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -187,6 +187,7 @@ "unittests/tsserver/projects.ts", "unittests/tsserver/refactors.ts", "unittests/tsserver/reload.ts", + "unittests/tsserver/reloadProjects.ts", "unittests/tsserver/rename.ts", "unittests/tsserver/resolutionCache.ts", "unittests/tsserver/session.ts", diff --git a/src/testRunner/unittests/config/commandLineParsing.ts b/src/testRunner/unittests/config/commandLineParsing.ts index 9fc9b0735b5c8..4239829c8c32e 100644 --- a/src/testRunner/unittests/config/commandLineParsing.ts +++ b/src/testRunner/unittests/config/commandLineParsing.ts @@ -663,6 +663,64 @@ namespace ts { watchOptions: { fallbackPolling: undefined } }); }); + + it("parse --excludeDirectories", () => { + assertParseResult(["--excludeDirectories", "**/temp", "0.ts"], + { + errors: [], + fileNames: ["0.ts"], + options: {}, + watchOptions: { excludeDirectories: ["**/temp"] } + }); + }); + + it("errors on invalid excludeDirectories", () => { + assertParseResult(["--excludeDirectories", "**/../*", "0.ts"], + { + errors: [ + { + messageText: `File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '**/../*'.`, + category: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.category, + code: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.code, + file: undefined, + start: undefined, + length: undefined + } + ], + fileNames: ["0.ts"], + options: {}, + watchOptions: { excludeDirectories: [] } + }); + }); + + it("parse --excludeFiles", () => { + assertParseResult(["--excludeFiles", "**/temp/*.ts", "0.ts"], + { + errors: [], + fileNames: ["0.ts"], + options: {}, + watchOptions: { excludeFiles: ["**/temp/*.ts"] } + }); + }); + + it("errors on invalid excludeFiles", () => { + assertParseResult(["--excludeFiles", "**/../*", "0.ts"], + { + errors: [ + { + messageText: `File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '**/../*'.`, + category: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.category, + code: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.code, + file: undefined, + start: undefined, + length: undefined + } + ], + fileNames: ["0.ts"], + options: {}, + watchOptions: { excludeFiles: [] } + }); + }); }); }); @@ -912,6 +970,54 @@ namespace ts { watchOptions: { fallbackPolling: undefined } }); }); + + it("errors on invalid excludeDirectories", () => { + assertParseResult(["--excludeDirectories", "**/../*"], + { + errors: [ + { + messageText: `File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '**/../*'.`, + category: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.category, + code: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.code, + file: undefined, + start: undefined, + length: undefined + } + ], + projects: ["."], + buildOptions: {}, + watchOptions: { excludeDirectories: [] } + }); + }); + + it("parse --excludeFiles", () => { + assertParseResult(["--excludeFiles", "**/temp/*.ts"], + { + errors: [], + projects: ["."], + buildOptions: {}, + watchOptions: { excludeFiles: ["**/temp/*.ts"] } + }); + }); + + it("errors on invalid excludeFiles", () => { + assertParseResult(["--excludeFiles", "**/../*"], + { + errors: [ + { + messageText: `File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '**/../*'.`, + category: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.category, + code: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.code, + file: undefined, + start: undefined, + length: undefined + } + ], + projects: ["."], + buildOptions: {}, + watchOptions: { excludeFiles: [] } + }); + }); }); }); } diff --git a/src/testRunner/unittests/config/tsconfigParsingWatchOptions.ts b/src/testRunner/unittests/config/tsconfigParsingWatchOptions.ts index 83e224e492527..120dd42d2c542 100644 --- a/src/testRunner/unittests/config/tsconfigParsingWatchOptions.ts +++ b/src/testRunner/unittests/config/tsconfigParsingWatchOptions.ts @@ -45,20 +45,33 @@ namespace ts { expectedOptions: WatchOptions | undefined; additionalFiles?: vfs.FileSet; existingWatchOptions?: WatchOptions | undefined; + expectedErrors?: (sourceFile?: SourceFile) => Diagnostic[]; } function verifyWatchOptions(scenario: () => VerifyWatchOptions[]) { it("with json api", () => { - for (const { json, expectedOptions, additionalFiles, existingWatchOptions } of scenario()) { + for (const { json, expectedOptions, additionalFiles, existingWatchOptions, expectedErrors } of scenario()) { const parsed = getParsedCommandJson(json, additionalFiles, existingWatchOptions); - assert.deepEqual(parsed.watchOptions, expectedOptions); + assert.deepEqual(parsed.watchOptions, expectedOptions, `With ${JSON.stringify(json)}`); + if (length(parsed.errors)) { + assert.deepEqual(parsed.errors, expectedErrors?.()); + } + else { + assert.equal(0, length(expectedErrors?.()), `Expected no errors`); + } } }); it("with json source file api", () => { - for (const { json, expectedOptions, additionalFiles, existingWatchOptions } of scenario()) { + for (const { json, expectedOptions, additionalFiles, existingWatchOptions, expectedErrors } of scenario()) { const parsed = getParsedCommandJsonNode(json, additionalFiles, existingWatchOptions); assert.deepEqual(parsed.watchOptions, expectedOptions); + if (length(parsed.errors)) { + assert.deepEqual(parsed.errors, expectedErrors?.(parsed.options.configFile)); + } + else { + assert.equal(0, length(expectedErrors?.(parsed.options.configFile)), `Expected no errors`); + } } }); } @@ -156,7 +169,47 @@ namespace ts { { json: { watchOptions: { synchronousWatchDirectory: true } }, expectedOptions: { synchronousWatchDirectory: true } - } + }, + { + json: { watchOptions: { excludeDirectories: ["**/temp"] } }, + expectedOptions: { excludeDirectories: ["/**/temp"] } + }, + { + json: { watchOptions: { excludeFiles: ["**/temp/*.ts"] } }, + expectedOptions: { excludeFiles: ["/**/temp/*.ts"] } + }, + { + json: { watchOptions: { excludeDirectories: ["**/../*"] } }, + expectedOptions: { excludeDirectories: [] }, + expectedErrors: sourceFile => [ + { + messageText: `File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '**/../*'.`, + category: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.category, + code: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.code, + file: sourceFile, + start: sourceFile && sourceFile.text.indexOf(`"**/../*"`), + length: sourceFile && `"**/../*"`.length, + reportsDeprecated: undefined, + reportsUnnecessary: undefined + } + ] + }, + { + json: { watchOptions: { excludeFiles: ["**/../*"] } }, + expectedOptions: { excludeFiles: [] }, + expectedErrors: sourceFile => [ + { + messageText: `File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '**/../*'.`, + category: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.category, + code: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.code, + file: sourceFile, + start: sourceFile && sourceFile.text.indexOf(`"**/../*"`), + length: sourceFile && `"**/../*"`.length, + reportsDeprecated: undefined, + reportsUnnecessary: undefined + } + ] + }, ]); }); diff --git a/src/testRunner/unittests/tscWatch/watchEnvironment.ts b/src/testRunner/unittests/tscWatch/watchEnvironment.ts index ff33daefa3053..b510cf6d2f673 100644 --- a/src/testRunner/unittests/tscWatch/watchEnvironment.ts +++ b/src/testRunner/unittests/tscWatch/watchEnvironment.ts @@ -411,6 +411,91 @@ namespace ts.tscWatch { }, changes: emptyArray }); + + describe("exclude options", () => { + function sys(watchOptions: WatchOptions, runWithoutRecursiveWatches?: boolean): WatchedSystem { + const configFile: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ exclude: ["node_modules"], watchOptions }) + }; + const main: File = { + path: `${projectRoot}/src/main.ts`, + content: `import { foo } from "bar"; foo();` + }; + const bar: File = { + path: `${projectRoot}/node_modules/bar/index.d.ts`, + content: `export { foo } from "./foo";` + }; + const foo: File = { + path: `${projectRoot}/node_modules/bar/foo.d.ts`, + content: `export function foo(): string;` + }; + const fooBar: File = { + path: `${projectRoot}/node_modules/bar/fooBar.d.ts`, + content: `export function fooBar(): string;` + }; + const temp: File = { + path: `${projectRoot}/node_modules/bar/temp/index.d.ts`, + content: "export function temp(): string;" + }; + const files = [libFile, main, bar, foo, fooBar, temp, configFile]; + return createWatchedSystem(files, { currentDirectory: projectRoot, runWithoutRecursiveWatches }); + } + + function verifyWorker(...additionalFlags: string[]) { + verifyTscWatch({ + scenario, + subScenario: `watchOptions/with excludeFiles option${additionalFlags.join("")}`, + commandLineArgs: ["-w", ...additionalFlags], + sys: () => sys({ excludeFiles: ["node_modules/*"] }), + changes: [ + { + caption: "Change foo", + change: sys => replaceFileText(sys, `${projectRoot}/node_modules/bar/foo.d.ts`, "foo", "fooBar"), + timeouts: sys => sys.checkTimeoutQueueLength(0), + } + ] + }); + + verifyTscWatch({ + scenario, + subScenario: `watchOptions/with excludeDirectories option${additionalFlags.join("")}`, + commandLineArgs: ["-w", ...additionalFlags], + sys: () => sys({ excludeDirectories: ["node_modules"] }), + changes: [ + { + caption: "delete fooBar", + change: sys => sys.deleteFile(`${projectRoot}/node_modules/bar/fooBar.d.ts`), + timeouts: sys => sys.checkTimeoutQueueLength(0), } + ] + }); + + verifyTscWatch({ + scenario, + subScenario: `watchOptions/with excludeDirectories option with recursive directory watching${additionalFlags.join("")}`, + commandLineArgs: ["-w", ...additionalFlags], + sys: () => sys({ excludeDirectories: ["**/temp"] }, /*runWithoutRecursiveWatches*/ true), + changes: [ + { + caption: "Directory watch updates because of main.js creation", + change: noop, + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(1); // To update directory callbacks for main.js output + sys.checkTimeoutQueueLength(0); + }, + }, + { + caption: "add new folder to temp", + change: sys => sys.ensureFileOrFolder({ path: `${projectRoot}/node_modules/bar/temp/fooBar/index.d.ts`, content: "export function temp(): string;" }), + timeouts: sys => sys.checkTimeoutQueueLength(0), + } + ] + }); + } + + verifyWorker(); + verifyWorker("-extendedDiagnostics"); + }); }); }); } diff --git a/src/testRunner/unittests/tsserver/reloadProjects.ts b/src/testRunner/unittests/tsserver/reloadProjects.ts new file mode 100644 index 0000000000000..970a9b2995eba --- /dev/null +++ b/src/testRunner/unittests/tsserver/reloadProjects.ts @@ -0,0 +1,139 @@ +namespace ts.projectSystem { + describe("unittests:: tsserver:: reloadProjects", () => { + const configFile: File = { + path: `${tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + watchOptions: { excludeDirectories: ["node_modules"] } + }) + }; + const file1: File = { + path: `${tscWatch.projectRoot}/file1.ts`, + content: `import { foo } from "module1"; + foo(); + import { bar } from "./file2"; + bar();` + }; + const file2: File = { + path: `${tscWatch.projectRoot}/file2.ts`, + content: `export function bar(){}` + }; + const moduleFile: File = { + path: `${tscWatch.projectRoot}/node_modules/module1/index.d.ts`, + content: `export function foo(): string;` + }; + + function verifyFileUpdates(host: TestServerHost, service: TestProjectService, project: server.Project) { + // update file + const updatedText = `${file2.content} + bar();`; + host.writeFile(file2.path, updatedText); + host.checkTimeoutQueueLength(0); + service.reloadProjects(); + assert.equal(project.getCurrentProgram()?.getSourceFile(file2.path)?.text, updatedText); + + // delete file + host.deleteFile(file2.path); + host.checkTimeoutQueueLength(0); + service.reloadProjects(); + assert.isUndefined(project.getCurrentProgram()?.getSourceFile(file2.path)?.text); + assert.isUndefined(service.getScriptInfo(file2.path)); + } + + it("configured project", () => { + const host = createServerHost([configFile, libFile, file1, file2]); + const service = createProjectService(host); + service.setHostConfiguration({ watchOptions: { excludeFiles: [file2.path] } }); + service.openClientFile(file1.path); + checkNumberOfProjects(service, { configuredProjects: 1 }); + const project = service.configuredProjects.get(configFile.path)!; + checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, configFile.path]); + + // Install module1 + host.ensureFileOrFolder(moduleFile); + host.checkTimeoutQueueLength(0); + + service.reloadProjects(); + checkNumberOfProjects(service, { configuredProjects: 1 }); + assert.strictEqual(service.configuredProjects.get(configFile.path), project); + checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, configFile.path, moduleFile.path]); + + verifyFileUpdates(host, service, project); + }); + + it("inferred project", () => { + const host = createServerHost([libFile, file1, file2]); + const service = createProjectService(host, /*parameters*/ undefined, { useInferredProjectPerProjectRoot: true, }); + service.setHostConfiguration({ watchOptions: { excludeFiles: [file2.path] } }); + const timeoutId = host.getNextTimeoutId(); + service.setCompilerOptionsForInferredProjects({ excludeDirectories: ["node_modules"] }, tscWatch.projectRoot); + host.clearTimeout(timeoutId); + service.openClientFile(file1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, tscWatch.projectRoot); + checkNumberOfProjects(service, { inferredProjects: 1 }); + const project = service.inferredProjects[0]; + checkProjectActualFiles(project, [libFile.path, file1.path, file2.path]); + + // Install module1 + host.ensureFileOrFolder(moduleFile); + host.checkTimeoutQueueLength(0); + + service.reloadProjects(); + checkNumberOfProjects(service, { inferredProjects: 1 }); + assert.strictEqual(service.inferredProjects[0], project); + checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, moduleFile.path]); + + verifyFileUpdates(host, service, project); + }); + + it("external project", () => { + const host = createServerHost([libFile, file1, file2]); + const service = createProjectService(host); + service.setHostConfiguration({ watchOptions: { excludeFiles: [file2.path] } }); + service.openExternalProject({ + projectFileName: `${tscWatch.projectRoot}/project.sln`, + options: { excludeDirectories: ["node_modules"] }, + rootFiles: [{ fileName: file1.path }, { fileName: file2.path }] + }); + service.openClientFile(file1.path); + checkNumberOfProjects(service, { externalProjects: 1 }); + const project = service.externalProjects[0]; + checkProjectActualFiles(project, [libFile.path, file1.path, file2.path]); + + // Install module1 + host.ensureFileOrFolder(moduleFile); + host.checkTimeoutQueueLength(0); + + service.reloadProjects(); + checkNumberOfProjects(service, { externalProjects: 1 }); + assert.strictEqual(service.externalProjects[0], project); + checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, moduleFile.path]); + + verifyFileUpdates(host, service, project); + }); + + it("external project with config file", () => { + const host = createServerHost([libFile, file1, file2, configFile]); + const service = createProjectService(host); + service.setHostConfiguration({ watchOptions: { excludeFiles: [file2.path] } }); + service.openExternalProject({ + projectFileName: `${tscWatch.projectRoot}/project.sln`, + options: { excludeDirectories: ["node_modules"] }, + rootFiles: [{ fileName: file1.path }, { fileName: file2.path }, { fileName: configFile.path }] + }); + service.openClientFile(file1.path); + checkNumberOfProjects(service, { configuredProjects: 1 }); + const project = service.configuredProjects.get(configFile.path)!; + checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, configFile.path]); + + // Install module1 + host.ensureFileOrFolder(moduleFile); + host.checkTimeoutQueueLength(0); + + service.reloadProjects(); + checkNumberOfProjects(service, { configuredProjects: 1 }); + assert.strictEqual(service.configuredProjects.get(configFile.path), project); + checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, configFile.path, moduleFile.path]); + + verifyFileUpdates(host, service, project); + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/watchEnvironment.ts b/src/testRunner/unittests/tsserver/watchEnvironment.ts index 49fc0b237f65e..854ac0d375c5e 100644 --- a/src/testRunner/unittests/tsserver/watchEnvironment.ts +++ b/src/testRunner/unittests/tsserver/watchEnvironment.ts @@ -565,6 +565,202 @@ namespace ts.projectSystem { checkWatchedDirectories(host, emptyArray, /*recursive*/ false); checkWatchedDirectories(host, emptyArray, /*recursive*/ true); }); + + describe("excludeDirectories", () => { + function setupFiles() { + const main: File = { + path: `${tscWatch.projectRoot}/src/main.ts`, + content: `import { foo } from "bar"; foo();` + }; + const bar: File = { + path: `${tscWatch.projectRoot}/node_modules/bar/index.d.ts`, + content: `export { foo } from "./foo";` + }; + const foo: File = { + path: `${tscWatch.projectRoot}/node_modules/bar/foo.d.ts`, + content: `export function foo(): string;` + }; + return { main, bar, foo }; + } + + function setupConfigureHost(service: TestProjectService, configureHost: boolean | undefined) { + if (configureHost) { + service.setHostConfiguration({ + watchOptions: { excludeDirectories: ["node_modules"] } + }); + } + } + function setup(configureHost?: boolean) { + const configFile: File = { + path: `${tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ include: ["src"], watchOptions: { excludeDirectories: ["node_modules"] } }) + }; + const { main, bar, foo } = setupFiles(); + const files = [libFile, main, bar, foo, configFile]; + const host = createServerHost(files, { currentDirectory: tscWatch.projectRoot }); + const service = createProjectService(host); + setupConfigureHost(service, configureHost); + service.openClientFile(main.path); + return { host, configFile }; + } + + it("with excludeDirectories option in configFile", () => { + const { host, configFile } = setup(); + checkWatchedFilesDetailed(host, [configFile.path, libFile.path], 1); + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); + checkWatchedDirectoriesDetailed( + host, + arrayToMap( + [`${tscWatch.projectRoot}/src`, `${tscWatch.projectRoot}/node_modules`], + identity, + f => f === `${tscWatch.projectRoot}/node_modules` ? 1 : 2, + ), + /*recursive*/ true, + ); + }); + + it("with excludeDirectories option in configuration", () => { + const { host, configFile } = setup(/*configureHost*/ true); + checkWatchedFilesDetailed(host, [configFile.path, libFile.path], 1); + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); + checkWatchedDirectoriesDetailed( + host, + [`${tscWatch.projectRoot}/src`], + 2, + /*recursive*/ true, + ); + }); + + function setupExternalProject(configureHost?: boolean) { + const { main, bar, foo } = setupFiles(); + const files = [libFile, main, bar, foo]; + const host = createServerHost(files, { currentDirectory: tscWatch.projectRoot }); + const service = createProjectService(host); + setupConfigureHost(service, configureHost); + service.openExternalProject({ + projectFileName: `${tscWatch.projectRoot}/project.csproj`, + rootFiles: toExternalFiles([main.path, bar.path, foo.path]), + options: { excludeDirectories: ["node_modules"] } + }); + service.openClientFile(main.path); + return host; + } + + it("external project watch options", () => { + const host = setupExternalProject(); + checkWatchedFilesDetailed(host, [libFile.path], 1); + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); + checkWatchedDirectoriesDetailed( + host, + [`${tscWatch.projectRoot}/src`, `${tscWatch.projectRoot}/node_modules`], + 1, + /*recursive*/ true, + ); + }); + + it("external project watch options in host configuration", () => { + const host = setupExternalProject(/*configureHost*/ true); + checkWatchedFilesDetailed(host, [libFile.path], 1); + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); + checkWatchedDirectoriesDetailed( + host, + [`${tscWatch.projectRoot}/src`], + 1, + /*recursive*/ true, + ); + }); + + it("external project watch options errors", () => { + const { main, bar, foo } = setupFiles(); + const files = [libFile, main, bar, foo]; + const host = createServerHost(files, { currentDirectory: tscWatch.projectRoot }); + const service = createProjectService(host); + service.openExternalProject({ + projectFileName: `${tscWatch.projectRoot}/project.csproj`, + rootFiles: toExternalFiles([main.path, bar.path, foo.path]), + options: { excludeDirectories: ["**/../*"] } + }); + service.openClientFile(main.path); + const project = service.externalProjects[0]; + assert.deepEqual(project.getAllProjectErrors(), [ + { + messageText: `File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '**/../*'.`, + category: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.category, + code: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.code, + file: undefined, + start: undefined, + length: undefined, + reportsDeprecated: undefined, + reportsUnnecessary: undefined, + } + ]); + }); + + function setupInferredProject(configureHost?: boolean) { + const { main, bar, foo } = setupFiles(); + const files = [libFile, main, bar, foo]; + const host = createServerHost(files, { currentDirectory: tscWatch.projectRoot }); + const service = createProjectService(host, {}, { useInferredProjectPerProjectRoot: true }); + setupConfigureHost(service, configureHost); + service.setCompilerOptionsForInferredProjects({ excludeDirectories: ["node_modules"] }, tscWatch.projectRoot); + service.openClientFile(main.path, main.content, ScriptKind.TS, tscWatch.projectRoot); + return host; + } + + it("inferred project watch options", () => { + const host = setupInferredProject(); + checkWatchedFilesDetailed( + host, + [libFile.path, `${tscWatch.projectRoot}/tsconfig.json`, `${tscWatch.projectRoot}/jsconfig.json`, `${tscWatch.projectRoot}/src/tsconfig.json`, `${tscWatch.projectRoot}/src/jsconfig.json`], + 1 + ); + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); + checkWatchedDirectoriesDetailed( + host, + [`${tscWatch.projectRoot}/src`, `${tscWatch.projectRoot}/node_modules`], + 1, + /*recursive*/ true, + ); + }); + + it("inferred project watch options in host configuration", () => { + const host = setupInferredProject(/*configureHost*/ true); + checkWatchedFilesDetailed( + host, + [libFile.path, `${tscWatch.projectRoot}/tsconfig.json`, `${tscWatch.projectRoot}/jsconfig.json`, `${tscWatch.projectRoot}/src/tsconfig.json`, `${tscWatch.projectRoot}/src/jsconfig.json`], + 1 + ); + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); + checkWatchedDirectoriesDetailed( + host, + [`${tscWatch.projectRoot}/src`], + 1, + /*recursive*/ true, + ); + }); + + it("inferred project watch options errors", () => { + const { main, bar, foo } = setupFiles(); + const files = [libFile, main, bar, foo]; + const host = createServerHost(files, { currentDirectory: tscWatch.projectRoot }); + const service = createProjectService(host, {}, { useInferredProjectPerProjectRoot: true }); + service.setCompilerOptionsForInferredProjects({ excludeDirectories: ["**/../*"] }, tscWatch.projectRoot); + service.openClientFile(main.path, main.content, ScriptKind.TS, tscWatch.projectRoot); + const project = service.inferredProjects[0]; + assert.deepEqual(project.getAllProjectErrors(), [ + { + messageText: `File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '**/../*'.`, + category: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.category, + code: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.code, + file: undefined, + start: undefined, + length: undefined, + reportsDeprecated: undefined, + reportsUnnecessary: undefined, + } + ]); + }); + }); }); describe("unittests:: tsserver:: watchEnvironment:: file names on case insensitive file system", () => { diff --git a/src/typingsInstallerCore/typingsInstaller.ts b/src/typingsInstallerCore/typingsInstaller.ts index 7966a78e46794..04e6e29473441 100644 --- a/src/typingsInstallerCore/typingsInstaller.ts +++ b/src/typingsInstallerCore/typingsInstaller.ts @@ -86,6 +86,10 @@ namespace ts.server.typingsInstaller { type ProjectWatchers = ESMap & { isInvoked?: boolean; }; + function getDetailWatchInfo(projectName: string, watchers: ProjectWatchers) { + return `Project: ${projectName} watcher already invoked: ${watchers.isInvoked}`; + } + export abstract class TypingsInstaller { private readonly packageNameToTypingLocation = new Map(); private readonly missingTypingsSet = new Set(); @@ -100,6 +104,8 @@ namespace ts.server.typingsInstaller { private inFlightRequestCount = 0; abstract readonly typesRegistry: ESMap>; + /*@internal*/ + private readonly watchFactory: WatchFactory; constructor( protected readonly installTypingHost: InstallTypingHost, @@ -110,9 +116,11 @@ namespace ts.server.typingsInstaller { protected readonly log = nullLog) { this.toCanonicalFileName = createGetCanonicalFileName(installTypingHost.useCaseSensitiveFileNames); this.globalCachePackageJsonPath = combinePaths(globalCachePath, "package.json"); - if (this.log.isEnabled()) { + const isLoggingEnabled = this.log.isEnabled(); + if (isLoggingEnabled) { this.log.writeLine(`Global cache location '${globalCachePath}', safe file path '${safeListPath}', types map path ${typesMapLocation}`); } + this.watchFactory = getWatchFactory(this.installTypingHost as WatchFactoryHost, isLoggingEnabled ? WatchLogLevel.Verbose : WatchLogLevel.None, s => this.log.writeLine(s), getDetailWatchInfo); this.processCacheLocation(this.globalCachePath); } @@ -431,19 +439,13 @@ namespace ts.server.typingsInstaller { this.log.writeLine(`${projectWatcherType}:: Added:: WatchInfo: ${path}`); } const watcher = projectWatcherType === ProjectWatcherType.FileWatcher ? - this.installTypingHost.watchFile!(path, (f, eventKind) => { // TODO: GH#18217 - if (isLoggingEnabled) { - this.log.writeLine(`FileWatcher:: Triggered with ${f} eventKind: ${FileWatcherEventKind[eventKind]}:: WatchInfo: ${path}:: handler is already invoked '${watchers.isInvoked}'`); - } + this.watchFactory.watchFile(path, () => { if (!watchers.isInvoked) { watchers.isInvoked = true; this.sendResponse({ projectName, kind: ActionInvalidate }); } - }, /*pollingInterval*/ 2000, options) : - this.installTypingHost.watchDirectory!(path, f => { // TODO: GH#18217 - if (isLoggingEnabled) { - this.log.writeLine(`DirectoryWatcher:: Triggered with ${f} :: WatchInfo: ${path} recursive :: handler is already invoked '${watchers.isInvoked}'`); - } + }, PollingInterval.High, options, projectName, watchers) : + this.watchFactory.watchDirectory(path, f => { if (watchers.isInvoked || !fileExtensionIs(f, Extension.Json)) { return; } @@ -453,7 +455,7 @@ namespace ts.server.typingsInstaller { watchers.isInvoked = true; this.sendResponse({ projectName, kind: ActionInvalidate }); } - }, /*recursive*/ true, options); + }, WatchDirectoryFlags.Recursive, options, projectName, watchers); watchers.set(canonicalPath, isLoggingEnabled ? { close: () => { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 7297e025b21da..0d780a2d47770 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2881,6 +2881,8 @@ declare namespace ts { watchDirectory?: WatchDirectoryKind; fallbackPolling?: PollingWatchKind; synchronousWatchDirectory?: boolean; + excludeDirectories?: string[]; + excludeFiles?: string[]; [option: string]: CompilerOptionsValue | undefined; } export interface TypeAcquisition { @@ -7583,6 +7585,8 @@ declare namespace ts.server.protocol { watchDirectory?: WatchDirectoryKind | ts.WatchDirectoryKind; fallbackPolling?: PollingWatchKind | ts.PollingWatchKind; synchronousWatchDirectory?: boolean; + excludeDirectories?: string[]; + excludeFiles?: string[]; [option: string]: CompilerOptionsValue | undefined; } /** @@ -9260,6 +9264,7 @@ declare namespace ts.server { * This property is different from projectStructureVersion since in most cases edits don't affect set of files in the project */ private projectStateVersion; + protected projectErrors: Diagnostic[] | undefined; protected isInitialLoadPending: () => boolean; private readonly cancellationToken; isNonTsProject(): boolean; @@ -9298,7 +9303,11 @@ declare namespace ts.server { * Get the errors that dont have any file name associated */ getGlobalProjectErrors(): readonly Diagnostic[]; + /** + * Get all the project errors + */ getAllProjectErrors(): readonly Diagnostic[]; + setProjectErrors(projectErrors: Diagnostic[] | undefined): void; getLanguageService(ensureSynchronized?: boolean): LanguageService; getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[]; /** @@ -9397,7 +9406,6 @@ declare namespace ts.server { readonly canonicalConfigFilePath: NormalizedPath; /** Ref count to the project when opened from external project */ private externalProjectRefCount; - private projectErrors; private projectReferences; /** * If the project has reload from disk pending, it reloads (and then updates graph as part of that) instead of just updating the graph @@ -9561,7 +9569,7 @@ declare namespace ts.server { } export function convertFormatOptions(protocolOptions: protocol.FormatCodeSettings): FormatCodeSettings; export function convertCompilerOptions(protocolOptions: protocol.ExternalProjectCompilerOptions): CompilerOptions & protocol.CompileOnSaveMixin; - export function convertWatchOptions(protocolOptions: protocol.ExternalProjectCompilerOptions): WatchOptions | undefined; + export function convertWatchOptions(protocolOptions: protocol.ExternalProjectCompilerOptions, currentDirectory?: string): WatchOptionsAndErrors | undefined; export function convertTypeAcquisition(protocolOptions: protocol.InferredProjectCompilerOptions): TypeAcquisition | undefined; export function tryConvertScriptKindName(scriptKindName: protocol.ScriptKindName | ScriptKind): ScriptKind; export function convertScriptKindName(scriptKindName: protocol.ScriptKindName): ScriptKind.Unknown | ScriptKind.JS | ScriptKind.JSX | ScriptKind.TS | ScriptKind.TSX; @@ -9594,6 +9602,10 @@ declare namespace ts.server { syntaxOnly?: boolean; serverMode?: LanguageServiceMode; } + export interface WatchOptionsAndErrors { + watchOptions: WatchOptions; + errors: Diagnostic[] | undefined; + } export class ProjectService { private readonly scriptInfoInNodeModulesWatchers; /** diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 5334a0da05457..5e9b51a30b8f6 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2881,6 +2881,8 @@ declare namespace ts { watchDirectory?: WatchDirectoryKind; fallbackPolling?: PollingWatchKind; synchronousWatchDirectory?: boolean; + excludeDirectories?: string[]; + excludeFiles?: string[]; [option: string]: CompilerOptionsValue | undefined; } export interface TypeAcquisition { diff --git a/tests/baselines/reference/showConfig/Shows tsconfig for single option/excludeDirectories/tsconfig.json b/tests/baselines/reference/showConfig/Shows tsconfig for single option/excludeDirectories/tsconfig.json new file mode 100644 index 0000000000000..743c9639c9f38 --- /dev/null +++ b/tests/baselines/reference/showConfig/Shows tsconfig for single option/excludeDirectories/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": {}, + "watchOptions": { + "excludeDirectories": [] + } +} diff --git a/tests/baselines/reference/showConfig/Shows tsconfig for single option/excludeFiles/tsconfig.json b/tests/baselines/reference/showConfig/Shows tsconfig for single option/excludeFiles/tsconfig.json new file mode 100644 index 0000000000000..3d35f093f6a68 --- /dev/null +++ b/tests/baselines/reference/showConfig/Shows tsconfig for single option/excludeFiles/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": {}, + "watchOptions": { + "excludeFiles": [] + } +} diff --git a/tests/baselines/reference/tscWatch/watchEnvironment/watchOptions/with-excludeDirectories-option-extendedDiagnostics.js b/tests/baselines/reference/tscWatch/watchEnvironment/watchOptions/with-excludeDirectories-option-extendedDiagnostics.js new file mode 100644 index 0000000000000..a6e2f90fcb77e --- /dev/null +++ b/tests/baselines/reference/tscWatch/watchEnvironment/watchOptions/with-excludeDirectories-option-extendedDiagnostics.js @@ -0,0 +1,133 @@ +Input:: +//// [/a/lib/lib.d.ts] +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } + +//// [/user/username/projects/myproject/src/main.ts] +import { foo } from "bar"; foo(); + +//// [/user/username/projects/myproject/node_modules/bar/index.d.ts] +export { foo } from "./foo"; + +//// [/user/username/projects/myproject/node_modules/bar/foo.d.ts] +export function foo(): string; + +//// [/user/username/projects/myproject/node_modules/bar/fooBar.d.ts] +export function fooBar(): string; + +//// [/user/username/projects/myproject/node_modules/bar/temp/index.d.ts] +export function temp(): string; + +//// [/user/username/projects/myproject/tsconfig.json] +{"exclude":["node_modules"],"watchOptions":{"excludeDirectories":["node_modules"]}} + + +/a/lib/tsc.js -w -extendedDiagnostics +Output:: +[12:00:37 AM] Starting compilation in watch mode... + +Current directory: /user/username/projects/myproject CaseSensitiveFileNames: false +FileWatcher:: Added:: WatchInfo: /user/username/projects/myproject/tsconfig.json 2000 {"excludeDirectories":["/user/username/projects/myproject/node_modules"]} Config file +Synchronizing program +CreatingProgramWith:: + roots: ["/user/username/projects/myproject/src/main.ts"] + options: {"watch":true,"extendedDiagnostics":true,"configFilePath":"/user/username/projects/myproject/tsconfig.json"} +FileWatcher:: Added:: WatchInfo: /user/username/projects/myproject/src/main.ts 250 {"excludeDirectories":["/user/username/projects/myproject/node_modules"]} Source file +FileWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules/bar/index.d.ts 250 {"excludeDirectories":["/user/username/projects/myproject/node_modules"]} Source file +ExcludeWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules 1 {"excludeDirectories":["/user/username/projects/myproject/node_modules"]} Failed Lookup Locations +FileWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules/bar/foo.d.ts 250 {"excludeDirectories":["/user/username/projects/myproject/node_modules"]} Source file +FileWatcher:: Added:: WatchInfo: /a/lib/lib.d.ts 250 {"excludeDirectories":["/user/username/projects/myproject/node_modules"]} Source file +DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/src 1 {"excludeDirectories":["/user/username/projects/myproject/node_modules"]} Failed Lookup Locations +Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/src 1 {"excludeDirectories":["/user/username/projects/myproject/node_modules"]} Failed Lookup Locations +ExcludeWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules/@types 1 {"excludeDirectories":["/user/username/projects/myproject/node_modules"]} Type roots +DirectoryWatcher:: Triggered with /user/username/projects/myproject/src/main.js :: WatchInfo: /user/username/projects/myproject/src 1 {"excludeDirectories":["/user/username/projects/myproject/node_modules"]} Failed Lookup Locations +Elapsed:: *ms DirectoryWatcher:: Triggered with /user/username/projects/myproject/src/main.js :: WatchInfo: /user/username/projects/myproject/src 1 {"excludeDirectories":["/user/username/projects/myproject/node_modules"]} Failed Lookup Locations +[12:00:40 AM] Found 0 errors. Watching for file changes. + +DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject 1 {"excludeDirectories":["/user/username/projects/myproject/node_modules"]} Wild card directory +Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject 1 {"excludeDirectories":["/user/username/projects/myproject/node_modules"]} Wild card directory + + +Program root files: ["/user/username/projects/myproject/src/main.ts"] +Program options: {"watch":true,"extendedDiagnostics":true,"configFilePath":"/user/username/projects/myproject/tsconfig.json"} +Program structureReused: Not +Program files:: +/a/lib/lib.d.ts +/user/username/projects/myproject/node_modules/bar/foo.d.ts +/user/username/projects/myproject/node_modules/bar/index.d.ts +/user/username/projects/myproject/src/main.ts + +Semantic diagnostics in builder refreshed for:: +/a/lib/lib.d.ts +/user/username/projects/myproject/node_modules/bar/foo.d.ts +/user/username/projects/myproject/node_modules/bar/index.d.ts +/user/username/projects/myproject/src/main.ts + +WatchedFiles:: +/user/username/projects/myproject/tsconfig.json: + {"fileName":"/user/username/projects/myproject/tsconfig.json","pollingInterval":250} +/user/username/projects/myproject/src/main.ts: + {"fileName":"/user/username/projects/myproject/src/main.ts","pollingInterval":250} +/user/username/projects/myproject/node_modules/bar/index.d.ts: + {"fileName":"/user/username/projects/myproject/node_modules/bar/index.d.ts","pollingInterval":250} +/user/username/projects/myproject/node_modules/bar/foo.d.ts: + {"fileName":"/user/username/projects/myproject/node_modules/bar/foo.d.ts","pollingInterval":250} +/a/lib/lib.d.ts: + {"fileName":"/a/lib/lib.d.ts","pollingInterval":250} + +FsWatches:: + +FsWatchesRecursive:: +/user/username/projects/myproject/src: + {"directoryName":"/user/username/projects/myproject/src","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject: + {"directoryName":"/user/username/projects/myproject","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} + +exitCode:: ExitStatus.undefined + +//// [/user/username/projects/myproject/src/main.js] +"use strict"; +exports.__esModule = true; +var bar_1 = require("bar"); +bar_1.foo(); + + + +Change:: delete fooBar + +Input:: +//// [/user/username/projects/myproject/node_modules/bar/fooBar.d.ts] deleted + +Output:: + +WatchedFiles:: +/user/username/projects/myproject/tsconfig.json: + {"fileName":"/user/username/projects/myproject/tsconfig.json","pollingInterval":250} +/user/username/projects/myproject/src/main.ts: + {"fileName":"/user/username/projects/myproject/src/main.ts","pollingInterval":250} +/user/username/projects/myproject/node_modules/bar/index.d.ts: + {"fileName":"/user/username/projects/myproject/node_modules/bar/index.d.ts","pollingInterval":250} +/user/username/projects/myproject/node_modules/bar/foo.d.ts: + {"fileName":"/user/username/projects/myproject/node_modules/bar/foo.d.ts","pollingInterval":250} +/a/lib/lib.d.ts: + {"fileName":"/a/lib/lib.d.ts","pollingInterval":250} + +FsWatches:: + +FsWatchesRecursive:: +/user/username/projects/myproject/src: + {"directoryName":"/user/username/projects/myproject/src","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject: + {"directoryName":"/user/username/projects/myproject","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} + +exitCode:: ExitStatus.undefined + diff --git a/tests/baselines/reference/tscWatch/watchEnvironment/watchOptions/with-excludeDirectories-option-with-recursive-directory-watching-extendedDiagnostics.js b/tests/baselines/reference/tscWatch/watchEnvironment/watchOptions/with-excludeDirectories-option-with-recursive-directory-watching-extendedDiagnostics.js new file mode 100644 index 0000000000000..1f4096cd6ba55 --- /dev/null +++ b/tests/baselines/reference/tscWatch/watchEnvironment/watchOptions/with-excludeDirectories-option-with-recursive-directory-watching-extendedDiagnostics.js @@ -0,0 +1,191 @@ +Input:: +//// [/a/lib/lib.d.ts] +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } + +//// [/user/username/projects/myproject/src/main.ts] +import { foo } from "bar"; foo(); + +//// [/user/username/projects/myproject/node_modules/bar/index.d.ts] +export { foo } from "./foo"; + +//// [/user/username/projects/myproject/node_modules/bar/foo.d.ts] +export function foo(): string; + +//// [/user/username/projects/myproject/node_modules/bar/fooBar.d.ts] +export function fooBar(): string; + +//// [/user/username/projects/myproject/node_modules/bar/temp/index.d.ts] +export function temp(): string; + +//// [/user/username/projects/myproject/tsconfig.json] +{"exclude":["node_modules"],"watchOptions":{"excludeDirectories":["**/temp"]}} + + +/a/lib/tsc.js -w -extendedDiagnostics +Output:: +[12:00:37 AM] Starting compilation in watch mode... + +Current directory: /user/username/projects/myproject CaseSensitiveFileNames: false +FileWatcher:: Added:: WatchInfo: /user/username/projects/myproject/tsconfig.json 2000 {"excludeDirectories":["/user/username/projects/myproject/**/temp"]} Config file +Synchronizing program +CreatingProgramWith:: + roots: ["/user/username/projects/myproject/src/main.ts"] + options: {"watch":true,"extendedDiagnostics":true,"configFilePath":"/user/username/projects/myproject/tsconfig.json"} +FileWatcher:: Added:: WatchInfo: /user/username/projects/myproject/src/main.ts 250 {"excludeDirectories":["/user/username/projects/myproject/**/temp"]} Source file +FileWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules/bar/index.d.ts 250 {"excludeDirectories":["/user/username/projects/myproject/**/temp"]} Source file +DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules 1 {"excludeDirectories":["/user/username/projects/myproject/**/temp"]} Failed Lookup Locations +Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules 1 {"excludeDirectories":["/user/username/projects/myproject/**/temp"]} Failed Lookup Locations +FileWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules/bar/foo.d.ts 250 {"excludeDirectories":["/user/username/projects/myproject/**/temp"]} Source file +FileWatcher:: Added:: WatchInfo: /a/lib/lib.d.ts 250 {"excludeDirectories":["/user/username/projects/myproject/**/temp"]} Source file +DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/src 1 {"excludeDirectories":["/user/username/projects/myproject/**/temp"]} Failed Lookup Locations +Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/src 1 {"excludeDirectories":["/user/username/projects/myproject/**/temp"]} Failed Lookup Locations +DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules/@types 1 {"excludeDirectories":["/user/username/projects/myproject/**/temp"]} Type roots +Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules/@types 1 {"excludeDirectories":["/user/username/projects/myproject/**/temp"]} Type roots +[12:00:40 AM] Found 0 errors. Watching for file changes. + +DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject 1 {"excludeDirectories":["/user/username/projects/myproject/**/temp"]} Wild card directory +Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject 1 {"excludeDirectories":["/user/username/projects/myproject/**/temp"]} Wild card directory + + +Program root files: ["/user/username/projects/myproject/src/main.ts"] +Program options: {"watch":true,"extendedDiagnostics":true,"configFilePath":"/user/username/projects/myproject/tsconfig.json"} +Program structureReused: Not +Program files:: +/a/lib/lib.d.ts +/user/username/projects/myproject/node_modules/bar/foo.d.ts +/user/username/projects/myproject/node_modules/bar/index.d.ts +/user/username/projects/myproject/src/main.ts + +Semantic diagnostics in builder refreshed for:: +/a/lib/lib.d.ts +/user/username/projects/myproject/node_modules/bar/foo.d.ts +/user/username/projects/myproject/node_modules/bar/index.d.ts +/user/username/projects/myproject/src/main.ts + +WatchedFiles:: +/user/username/projects/myproject/tsconfig.json: + {"fileName":"/user/username/projects/myproject/tsconfig.json","pollingInterval":250} +/user/username/projects/myproject/src/main.ts: + {"fileName":"/user/username/projects/myproject/src/main.ts","pollingInterval":250} +/user/username/projects/myproject/node_modules/bar/index.d.ts: + {"fileName":"/user/username/projects/myproject/node_modules/bar/index.d.ts","pollingInterval":250} +/user/username/projects/myproject/node_modules/bar/foo.d.ts: + {"fileName":"/user/username/projects/myproject/node_modules/bar/foo.d.ts","pollingInterval":250} +/a/lib/lib.d.ts: + {"fileName":"/a/lib/lib.d.ts","pollingInterval":250} + +FsWatches:: +/user/username/projects/myproject/node_modules: + {"directoryName":"/user/username/projects/myproject/node_modules","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject/node_modules/bar: + {"directoryName":"/user/username/projects/myproject/node_modules/bar","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject/src: + {"directoryName":"/user/username/projects/myproject/src","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject/node_modules/@types: + {"directoryName":"/user/username/projects/myproject/node_modules/@types","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject: + {"directoryName":"/user/username/projects/myproject","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} + +FsWatchesRecursive:: + +exitCode:: ExitStatus.undefined + +//// [/user/username/projects/myproject/src/main.js] +"use strict"; +exports.__esModule = true; +var bar_1 = require("bar"); +bar_1.foo(); + + + +Change:: Directory watch updates because of main.js creation + +Input:: + +Output:: +sysLog:: onTimerToUpdateChildWatches:: 1 +sysLog:: invokingWatchers:: Elapsed:: *ms:: 0 +DirectoryWatcher:: Triggered with /user/username/projects/myproject/src/main.js 0:: WatchInfo: /user/username/projects/myproject/src 1 {"excludeDirectories":["/user/username/projects/myproject/**/temp"]} Failed Lookup Locations +Elapsed:: *ms DirectoryWatcher:: Triggered with /user/username/projects/myproject/src/main.js 0:: WatchInfo: /user/username/projects/myproject/src 1 {"excludeDirectories":["/user/username/projects/myproject/**/temp"]} Failed Lookup Locations +DirectoryWatcher:: Triggered with /user/username/projects/myproject/src/main.js 0:: WatchInfo: /user/username/projects/myproject 1 {"excludeDirectories":["/user/username/projects/myproject/**/temp"]} Wild card directory +Project: /user/username/projects/myproject/tsconfig.json Detected file add/remove of non supported extension: /user/username/projects/myproject/src/main.js +Elapsed:: *ms DirectoryWatcher:: Triggered with /user/username/projects/myproject/src/main.js 0:: WatchInfo: /user/username/projects/myproject 1 {"excludeDirectories":["/user/username/projects/myproject/**/temp"]} Wild card directory +sysLog:: Elapsed:: *ms:: onTimerToUpdateChildWatches:: 0 undefined + + +WatchedFiles:: +/user/username/projects/myproject/tsconfig.json: + {"fileName":"/user/username/projects/myproject/tsconfig.json","pollingInterval":250} +/user/username/projects/myproject/src/main.ts: + {"fileName":"/user/username/projects/myproject/src/main.ts","pollingInterval":250} +/user/username/projects/myproject/node_modules/bar/index.d.ts: + {"fileName":"/user/username/projects/myproject/node_modules/bar/index.d.ts","pollingInterval":250} +/user/username/projects/myproject/node_modules/bar/foo.d.ts: + {"fileName":"/user/username/projects/myproject/node_modules/bar/foo.d.ts","pollingInterval":250} +/a/lib/lib.d.ts: + {"fileName":"/a/lib/lib.d.ts","pollingInterval":250} + +FsWatches:: +/user/username/projects/myproject/node_modules: + {"directoryName":"/user/username/projects/myproject/node_modules","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject/node_modules/bar: + {"directoryName":"/user/username/projects/myproject/node_modules/bar","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject/src: + {"directoryName":"/user/username/projects/myproject/src","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject/node_modules/@types: + {"directoryName":"/user/username/projects/myproject/node_modules/@types","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject: + {"directoryName":"/user/username/projects/myproject","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} + +FsWatchesRecursive:: + +exitCode:: ExitStatus.undefined + + +Change:: add new folder to temp + +Input:: +//// [/user/username/projects/myproject/node_modules/bar/temp/fooBar/index.d.ts] +export function temp(): string; + + +Output:: + +WatchedFiles:: +/user/username/projects/myproject/tsconfig.json: + {"fileName":"/user/username/projects/myproject/tsconfig.json","pollingInterval":250} +/user/username/projects/myproject/src/main.ts: + {"fileName":"/user/username/projects/myproject/src/main.ts","pollingInterval":250} +/user/username/projects/myproject/node_modules/bar/index.d.ts: + {"fileName":"/user/username/projects/myproject/node_modules/bar/index.d.ts","pollingInterval":250} +/user/username/projects/myproject/node_modules/bar/foo.d.ts: + {"fileName":"/user/username/projects/myproject/node_modules/bar/foo.d.ts","pollingInterval":250} +/a/lib/lib.d.ts: + {"fileName":"/a/lib/lib.d.ts","pollingInterval":250} + +FsWatches:: +/user/username/projects/myproject/node_modules: + {"directoryName":"/user/username/projects/myproject/node_modules","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject/node_modules/bar: + {"directoryName":"/user/username/projects/myproject/node_modules/bar","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject/src: + {"directoryName":"/user/username/projects/myproject/src","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject/node_modules/@types: + {"directoryName":"/user/username/projects/myproject/node_modules/@types","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject: + {"directoryName":"/user/username/projects/myproject","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} + +FsWatchesRecursive:: + +exitCode:: ExitStatus.undefined + diff --git a/tests/baselines/reference/tscWatch/watchEnvironment/watchOptions/with-excludeDirectories-option-with-recursive-directory-watching.js b/tests/baselines/reference/tscWatch/watchEnvironment/watchOptions/with-excludeDirectories-option-with-recursive-directory-watching.js new file mode 100644 index 0000000000000..bf68efd365922 --- /dev/null +++ b/tests/baselines/reference/tscWatch/watchEnvironment/watchOptions/with-excludeDirectories-option-with-recursive-directory-watching.js @@ -0,0 +1,165 @@ +Input:: +//// [/a/lib/lib.d.ts] +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } + +//// [/user/username/projects/myproject/src/main.ts] +import { foo } from "bar"; foo(); + +//// [/user/username/projects/myproject/node_modules/bar/index.d.ts] +export { foo } from "./foo"; + +//// [/user/username/projects/myproject/node_modules/bar/foo.d.ts] +export function foo(): string; + +//// [/user/username/projects/myproject/node_modules/bar/fooBar.d.ts] +export function fooBar(): string; + +//// [/user/username/projects/myproject/node_modules/bar/temp/index.d.ts] +export function temp(): string; + +//// [/user/username/projects/myproject/tsconfig.json] +{"exclude":["node_modules"],"watchOptions":{"excludeDirectories":["**/temp"]}} + + +/a/lib/tsc.js -w +Output:: +>> Screen clear +[12:00:37 AM] Starting compilation in watch mode... + +[12:00:40 AM] Found 0 errors. Watching for file changes. + + + +Program root files: ["/user/username/projects/myproject/src/main.ts"] +Program options: {"watch":true,"configFilePath":"/user/username/projects/myproject/tsconfig.json"} +Program structureReused: Not +Program files:: +/a/lib/lib.d.ts +/user/username/projects/myproject/node_modules/bar/foo.d.ts +/user/username/projects/myproject/node_modules/bar/index.d.ts +/user/username/projects/myproject/src/main.ts + +Semantic diagnostics in builder refreshed for:: +/a/lib/lib.d.ts +/user/username/projects/myproject/node_modules/bar/foo.d.ts +/user/username/projects/myproject/node_modules/bar/index.d.ts +/user/username/projects/myproject/src/main.ts + +WatchedFiles:: +/user/username/projects/myproject/tsconfig.json: + {"fileName":"/user/username/projects/myproject/tsconfig.json","pollingInterval":250} +/user/username/projects/myproject/src/main.ts: + {"fileName":"/user/username/projects/myproject/src/main.ts","pollingInterval":250} +/user/username/projects/myproject/node_modules/bar/index.d.ts: + {"fileName":"/user/username/projects/myproject/node_modules/bar/index.d.ts","pollingInterval":250} +/user/username/projects/myproject/node_modules/bar/foo.d.ts: + {"fileName":"/user/username/projects/myproject/node_modules/bar/foo.d.ts","pollingInterval":250} +/a/lib/lib.d.ts: + {"fileName":"/a/lib/lib.d.ts","pollingInterval":250} + +FsWatches:: +/user/username/projects/myproject/node_modules: + {"directoryName":"/user/username/projects/myproject/node_modules","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject/node_modules/bar: + {"directoryName":"/user/username/projects/myproject/node_modules/bar","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject/src: + {"directoryName":"/user/username/projects/myproject/src","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject/node_modules/@types: + {"directoryName":"/user/username/projects/myproject/node_modules/@types","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject: + {"directoryName":"/user/username/projects/myproject","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} + +FsWatchesRecursive:: + +exitCode:: ExitStatus.undefined + +//// [/user/username/projects/myproject/src/main.js] +"use strict"; +exports.__esModule = true; +var bar_1 = require("bar"); +bar_1.foo(); + + + +Change:: Directory watch updates because of main.js creation + +Input:: + +Output:: + +WatchedFiles:: +/user/username/projects/myproject/tsconfig.json: + {"fileName":"/user/username/projects/myproject/tsconfig.json","pollingInterval":250} +/user/username/projects/myproject/src/main.ts: + {"fileName":"/user/username/projects/myproject/src/main.ts","pollingInterval":250} +/user/username/projects/myproject/node_modules/bar/index.d.ts: + {"fileName":"/user/username/projects/myproject/node_modules/bar/index.d.ts","pollingInterval":250} +/user/username/projects/myproject/node_modules/bar/foo.d.ts: + {"fileName":"/user/username/projects/myproject/node_modules/bar/foo.d.ts","pollingInterval":250} +/a/lib/lib.d.ts: + {"fileName":"/a/lib/lib.d.ts","pollingInterval":250} + +FsWatches:: +/user/username/projects/myproject/node_modules: + {"directoryName":"/user/username/projects/myproject/node_modules","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject/node_modules/bar: + {"directoryName":"/user/username/projects/myproject/node_modules/bar","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject/src: + {"directoryName":"/user/username/projects/myproject/src","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject/node_modules/@types: + {"directoryName":"/user/username/projects/myproject/node_modules/@types","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject: + {"directoryName":"/user/username/projects/myproject","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} + +FsWatchesRecursive:: + +exitCode:: ExitStatus.undefined + + +Change:: add new folder to temp + +Input:: +//// [/user/username/projects/myproject/node_modules/bar/temp/fooBar/index.d.ts] +export function temp(): string; + + +Output:: + +WatchedFiles:: +/user/username/projects/myproject/tsconfig.json: + {"fileName":"/user/username/projects/myproject/tsconfig.json","pollingInterval":250} +/user/username/projects/myproject/src/main.ts: + {"fileName":"/user/username/projects/myproject/src/main.ts","pollingInterval":250} +/user/username/projects/myproject/node_modules/bar/index.d.ts: + {"fileName":"/user/username/projects/myproject/node_modules/bar/index.d.ts","pollingInterval":250} +/user/username/projects/myproject/node_modules/bar/foo.d.ts: + {"fileName":"/user/username/projects/myproject/node_modules/bar/foo.d.ts","pollingInterval":250} +/a/lib/lib.d.ts: + {"fileName":"/a/lib/lib.d.ts","pollingInterval":250} + +FsWatches:: +/user/username/projects/myproject/node_modules: + {"directoryName":"/user/username/projects/myproject/node_modules","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject/node_modules/bar: + {"directoryName":"/user/username/projects/myproject/node_modules/bar","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject/src: + {"directoryName":"/user/username/projects/myproject/src","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject/node_modules/@types: + {"directoryName":"/user/username/projects/myproject/node_modules/@types","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject: + {"directoryName":"/user/username/projects/myproject","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} + +FsWatchesRecursive:: + +exitCode:: ExitStatus.undefined + diff --git a/tests/baselines/reference/tscWatch/watchEnvironment/watchOptions/with-excludeDirectories-option.js b/tests/baselines/reference/tscWatch/watchEnvironment/watchOptions/with-excludeDirectories-option.js new file mode 100644 index 0000000000000..d08109a4b1741 --- /dev/null +++ b/tests/baselines/reference/tscWatch/watchEnvironment/watchOptions/with-excludeDirectories-option.js @@ -0,0 +1,116 @@ +Input:: +//// [/a/lib/lib.d.ts] +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } + +//// [/user/username/projects/myproject/src/main.ts] +import { foo } from "bar"; foo(); + +//// [/user/username/projects/myproject/node_modules/bar/index.d.ts] +export { foo } from "./foo"; + +//// [/user/username/projects/myproject/node_modules/bar/foo.d.ts] +export function foo(): string; + +//// [/user/username/projects/myproject/node_modules/bar/fooBar.d.ts] +export function fooBar(): string; + +//// [/user/username/projects/myproject/node_modules/bar/temp/index.d.ts] +export function temp(): string; + +//// [/user/username/projects/myproject/tsconfig.json] +{"exclude":["node_modules"],"watchOptions":{"excludeDirectories":["node_modules"]}} + + +/a/lib/tsc.js -w +Output:: +>> Screen clear +[12:00:37 AM] Starting compilation in watch mode... + +[12:00:40 AM] Found 0 errors. Watching for file changes. + + + +Program root files: ["/user/username/projects/myproject/src/main.ts"] +Program options: {"watch":true,"configFilePath":"/user/username/projects/myproject/tsconfig.json"} +Program structureReused: Not +Program files:: +/a/lib/lib.d.ts +/user/username/projects/myproject/node_modules/bar/foo.d.ts +/user/username/projects/myproject/node_modules/bar/index.d.ts +/user/username/projects/myproject/src/main.ts + +Semantic diagnostics in builder refreshed for:: +/a/lib/lib.d.ts +/user/username/projects/myproject/node_modules/bar/foo.d.ts +/user/username/projects/myproject/node_modules/bar/index.d.ts +/user/username/projects/myproject/src/main.ts + +WatchedFiles:: +/user/username/projects/myproject/tsconfig.json: + {"fileName":"/user/username/projects/myproject/tsconfig.json","pollingInterval":250} +/user/username/projects/myproject/src/main.ts: + {"fileName":"/user/username/projects/myproject/src/main.ts","pollingInterval":250} +/user/username/projects/myproject/node_modules/bar/index.d.ts: + {"fileName":"/user/username/projects/myproject/node_modules/bar/index.d.ts","pollingInterval":250} +/user/username/projects/myproject/node_modules/bar/foo.d.ts: + {"fileName":"/user/username/projects/myproject/node_modules/bar/foo.d.ts","pollingInterval":250} +/a/lib/lib.d.ts: + {"fileName":"/a/lib/lib.d.ts","pollingInterval":250} + +FsWatches:: + +FsWatchesRecursive:: +/user/username/projects/myproject/src: + {"directoryName":"/user/username/projects/myproject/src","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject: + {"directoryName":"/user/username/projects/myproject","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} + +exitCode:: ExitStatus.undefined + +//// [/user/username/projects/myproject/src/main.js] +"use strict"; +exports.__esModule = true; +var bar_1 = require("bar"); +bar_1.foo(); + + + +Change:: delete fooBar + +Input:: +//// [/user/username/projects/myproject/node_modules/bar/fooBar.d.ts] deleted + +Output:: + +WatchedFiles:: +/user/username/projects/myproject/tsconfig.json: + {"fileName":"/user/username/projects/myproject/tsconfig.json","pollingInterval":250} +/user/username/projects/myproject/src/main.ts: + {"fileName":"/user/username/projects/myproject/src/main.ts","pollingInterval":250} +/user/username/projects/myproject/node_modules/bar/index.d.ts: + {"fileName":"/user/username/projects/myproject/node_modules/bar/index.d.ts","pollingInterval":250} +/user/username/projects/myproject/node_modules/bar/foo.d.ts: + {"fileName":"/user/username/projects/myproject/node_modules/bar/foo.d.ts","pollingInterval":250} +/a/lib/lib.d.ts: + {"fileName":"/a/lib/lib.d.ts","pollingInterval":250} + +FsWatches:: + +FsWatchesRecursive:: +/user/username/projects/myproject/src: + {"directoryName":"/user/username/projects/myproject/src","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject: + {"directoryName":"/user/username/projects/myproject","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} + +exitCode:: ExitStatus.undefined + diff --git a/tests/baselines/reference/tscWatch/watchEnvironment/watchOptions/with-excludeFiles-option-extendedDiagnostics.js b/tests/baselines/reference/tscWatch/watchEnvironment/watchOptions/with-excludeFiles-option-extendedDiagnostics.js new file mode 100644 index 0000000000000..05b12d4c9514b --- /dev/null +++ b/tests/baselines/reference/tscWatch/watchEnvironment/watchOptions/with-excludeFiles-option-extendedDiagnostics.js @@ -0,0 +1,137 @@ +Input:: +//// [/a/lib/lib.d.ts] +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } + +//// [/user/username/projects/myproject/src/main.ts] +import { foo } from "bar"; foo(); + +//// [/user/username/projects/myproject/node_modules/bar/index.d.ts] +export { foo } from "./foo"; + +//// [/user/username/projects/myproject/node_modules/bar/foo.d.ts] +export function foo(): string; + +//// [/user/username/projects/myproject/node_modules/bar/fooBar.d.ts] +export function fooBar(): string; + +//// [/user/username/projects/myproject/node_modules/bar/temp/index.d.ts] +export function temp(): string; + +//// [/user/username/projects/myproject/tsconfig.json] +{"exclude":["node_modules"],"watchOptions":{"excludeFiles":["node_modules/*"]}} + + +/a/lib/tsc.js -w -extendedDiagnostics +Output:: +[12:00:37 AM] Starting compilation in watch mode... + +Current directory: /user/username/projects/myproject CaseSensitiveFileNames: false +FileWatcher:: Added:: WatchInfo: /user/username/projects/myproject/tsconfig.json 2000 {"excludeFiles":["/user/username/projects/myproject/node_modules/*"]} Config file +Synchronizing program +CreatingProgramWith:: + roots: ["/user/username/projects/myproject/src/main.ts"] + options: {"watch":true,"extendedDiagnostics":true,"configFilePath":"/user/username/projects/myproject/tsconfig.json"} +FileWatcher:: Added:: WatchInfo: /user/username/projects/myproject/src/main.ts 250 {"excludeFiles":["/user/username/projects/myproject/node_modules/*"]} Source file +ExcludeWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules/bar/index.d.ts 250 {"excludeFiles":["/user/username/projects/myproject/node_modules/*"]} Source file +DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules 1 {"excludeFiles":["/user/username/projects/myproject/node_modules/*"]} Failed Lookup Locations +Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules 1 {"excludeFiles":["/user/username/projects/myproject/node_modules/*"]} Failed Lookup Locations +ExcludeWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules/bar/foo.d.ts 250 {"excludeFiles":["/user/username/projects/myproject/node_modules/*"]} Source file +FileWatcher:: Added:: WatchInfo: /a/lib/lib.d.ts 250 {"excludeFiles":["/user/username/projects/myproject/node_modules/*"]} Source file +DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/src 1 {"excludeFiles":["/user/username/projects/myproject/node_modules/*"]} Failed Lookup Locations +Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/src 1 {"excludeFiles":["/user/username/projects/myproject/node_modules/*"]} Failed Lookup Locations +DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules/@types 1 {"excludeFiles":["/user/username/projects/myproject/node_modules/*"]} Type roots +Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules/@types 1 {"excludeFiles":["/user/username/projects/myproject/node_modules/*"]} Type roots +DirectoryWatcher:: Triggered with /user/username/projects/myproject/src/main.js :: WatchInfo: /user/username/projects/myproject/src 1 {"excludeFiles":["/user/username/projects/myproject/node_modules/*"]} Failed Lookup Locations +Elapsed:: *ms DirectoryWatcher:: Triggered with /user/username/projects/myproject/src/main.js :: WatchInfo: /user/username/projects/myproject/src 1 {"excludeFiles":["/user/username/projects/myproject/node_modules/*"]} Failed Lookup Locations +[12:00:40 AM] Found 0 errors. Watching for file changes. + +DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject 1 {"excludeFiles":["/user/username/projects/myproject/node_modules/*"]} Wild card directory +Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject 1 {"excludeFiles":["/user/username/projects/myproject/node_modules/*"]} Wild card directory + + +Program root files: ["/user/username/projects/myproject/src/main.ts"] +Program options: {"watch":true,"extendedDiagnostics":true,"configFilePath":"/user/username/projects/myproject/tsconfig.json"} +Program structureReused: Not +Program files:: +/a/lib/lib.d.ts +/user/username/projects/myproject/node_modules/bar/foo.d.ts +/user/username/projects/myproject/node_modules/bar/index.d.ts +/user/username/projects/myproject/src/main.ts + +Semantic diagnostics in builder refreshed for:: +/a/lib/lib.d.ts +/user/username/projects/myproject/node_modules/bar/foo.d.ts +/user/username/projects/myproject/node_modules/bar/index.d.ts +/user/username/projects/myproject/src/main.ts + +WatchedFiles:: +/user/username/projects/myproject/tsconfig.json: + {"fileName":"/user/username/projects/myproject/tsconfig.json","pollingInterval":250} +/user/username/projects/myproject/src/main.ts: + {"fileName":"/user/username/projects/myproject/src/main.ts","pollingInterval":250} +/a/lib/lib.d.ts: + {"fileName":"/a/lib/lib.d.ts","pollingInterval":250} + +FsWatches:: + +FsWatchesRecursive:: +/user/username/projects/myproject/node_modules: + {"directoryName":"/user/username/projects/myproject/node_modules","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject/src: + {"directoryName":"/user/username/projects/myproject/src","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject/node_modules/@types: + {"directoryName":"/user/username/projects/myproject/node_modules/@types","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject: + {"directoryName":"/user/username/projects/myproject","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} + +exitCode:: ExitStatus.undefined + +//// [/user/username/projects/myproject/src/main.js] +"use strict"; +exports.__esModule = true; +var bar_1 = require("bar"); +bar_1.foo(); + + + +Change:: Change foo + +Input:: +//// [/user/username/projects/myproject/node_modules/bar/foo.d.ts] +export function fooBar(): string; + + +Output:: + +WatchedFiles:: +/user/username/projects/myproject/tsconfig.json: + {"fileName":"/user/username/projects/myproject/tsconfig.json","pollingInterval":250} +/user/username/projects/myproject/src/main.ts: + {"fileName":"/user/username/projects/myproject/src/main.ts","pollingInterval":250} +/a/lib/lib.d.ts: + {"fileName":"/a/lib/lib.d.ts","pollingInterval":250} + +FsWatches:: + +FsWatchesRecursive:: +/user/username/projects/myproject/node_modules: + {"directoryName":"/user/username/projects/myproject/node_modules","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject/src: + {"directoryName":"/user/username/projects/myproject/src","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject/node_modules/@types: + {"directoryName":"/user/username/projects/myproject/node_modules/@types","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject: + {"directoryName":"/user/username/projects/myproject","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} + +exitCode:: ExitStatus.undefined + diff --git a/tests/baselines/reference/tscWatch/watchEnvironment/watchOptions/with-excludeFiles-option.js b/tests/baselines/reference/tscWatch/watchEnvironment/watchOptions/with-excludeFiles-option.js new file mode 100644 index 0000000000000..be15b0b3e5405 --- /dev/null +++ b/tests/baselines/reference/tscWatch/watchEnvironment/watchOptions/with-excludeFiles-option.js @@ -0,0 +1,118 @@ +Input:: +//// [/a/lib/lib.d.ts] +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } + +//// [/user/username/projects/myproject/src/main.ts] +import { foo } from "bar"; foo(); + +//// [/user/username/projects/myproject/node_modules/bar/index.d.ts] +export { foo } from "./foo"; + +//// [/user/username/projects/myproject/node_modules/bar/foo.d.ts] +export function foo(): string; + +//// [/user/username/projects/myproject/node_modules/bar/fooBar.d.ts] +export function fooBar(): string; + +//// [/user/username/projects/myproject/node_modules/bar/temp/index.d.ts] +export function temp(): string; + +//// [/user/username/projects/myproject/tsconfig.json] +{"exclude":["node_modules"],"watchOptions":{"excludeFiles":["node_modules/*"]}} + + +/a/lib/tsc.js -w +Output:: +>> Screen clear +[12:00:37 AM] Starting compilation in watch mode... + +[12:00:40 AM] Found 0 errors. Watching for file changes. + + + +Program root files: ["/user/username/projects/myproject/src/main.ts"] +Program options: {"watch":true,"configFilePath":"/user/username/projects/myproject/tsconfig.json"} +Program structureReused: Not +Program files:: +/a/lib/lib.d.ts +/user/username/projects/myproject/node_modules/bar/foo.d.ts +/user/username/projects/myproject/node_modules/bar/index.d.ts +/user/username/projects/myproject/src/main.ts + +Semantic diagnostics in builder refreshed for:: +/a/lib/lib.d.ts +/user/username/projects/myproject/node_modules/bar/foo.d.ts +/user/username/projects/myproject/node_modules/bar/index.d.ts +/user/username/projects/myproject/src/main.ts + +WatchedFiles:: +/user/username/projects/myproject/tsconfig.json: + {"fileName":"/user/username/projects/myproject/tsconfig.json","pollingInterval":250} +/user/username/projects/myproject/src/main.ts: + {"fileName":"/user/username/projects/myproject/src/main.ts","pollingInterval":250} +/a/lib/lib.d.ts: + {"fileName":"/a/lib/lib.d.ts","pollingInterval":250} + +FsWatches:: + +FsWatchesRecursive:: +/user/username/projects/myproject/node_modules: + {"directoryName":"/user/username/projects/myproject/node_modules","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject/src: + {"directoryName":"/user/username/projects/myproject/src","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject/node_modules/@types: + {"directoryName":"/user/username/projects/myproject/node_modules/@types","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject: + {"directoryName":"/user/username/projects/myproject","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} + +exitCode:: ExitStatus.undefined + +//// [/user/username/projects/myproject/src/main.js] +"use strict"; +exports.__esModule = true; +var bar_1 = require("bar"); +bar_1.foo(); + + + +Change:: Change foo + +Input:: +//// [/user/username/projects/myproject/node_modules/bar/foo.d.ts] +export function fooBar(): string; + + +Output:: + +WatchedFiles:: +/user/username/projects/myproject/tsconfig.json: + {"fileName":"/user/username/projects/myproject/tsconfig.json","pollingInterval":250} +/user/username/projects/myproject/src/main.ts: + {"fileName":"/user/username/projects/myproject/src/main.ts","pollingInterval":250} +/a/lib/lib.d.ts: + {"fileName":"/a/lib/lib.d.ts","pollingInterval":250} + +FsWatches:: + +FsWatchesRecursive:: +/user/username/projects/myproject/node_modules: + {"directoryName":"/user/username/projects/myproject/node_modules","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject/src: + {"directoryName":"/user/username/projects/myproject/src","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject/node_modules/@types: + {"directoryName":"/user/username/projects/myproject/node_modules/@types","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/user/username/projects/myproject: + {"directoryName":"/user/username/projects/myproject","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} + +exitCode:: ExitStatus.undefined +