1declare function setTimeout(handler: (...args: any[]) => void, timeout: number): any; 2declare function clearTimeout(handle: any): void; 3 4namespace ts { 5 /** 6 * djb2 hashing algorithm 7 * http://www.cse.yorku.ca/~oz/hash.html 8 */ 9 /* @internal */ 10 export function generateDjb2Hash(data: string): string { 11 let acc = 5381; 12 for (let i = 0; i < data.length; i++) { 13 acc = ((acc << 5) + acc) + data.charCodeAt(i); 14 } 15 return acc.toString(); 16 } 17 18 /** 19 * Set a high stack trace limit to provide more information in case of an error. 20 * Called for command-line and server use cases. 21 * Not called if TypeScript is used as a library. 22 */ 23 /* @internal */ 24 export function setStackTraceLimit() { 25 if ((Error as any).stackTraceLimit < 100) { // Also tests that we won't set the property if it doesn't exist. 26 (Error as any).stackTraceLimit = 100; 27 } 28 } 29 30 export enum FileWatcherEventKind { 31 Created, 32 Changed, 33 Deleted 34 } 35 36 export type FileWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind) => void; 37 export type DirectoryWatcherCallback = (fileName: string) => void; 38 /*@internal*/ 39 export interface WatchedFile { 40 readonly fileName: string; 41 readonly callback: FileWatcherCallback; 42 mtime: Date; 43 } 44 45 /* @internal */ 46 export enum PollingInterval { 47 High = 2000, 48 Medium = 500, 49 Low = 250 50 } 51 52 /* @internal */ 53 export type HostWatchFile = (fileName: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined) => FileWatcher; 54 /* @internal */ 55 export type HostWatchDirectory = (fileName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined) => FileWatcher; 56 57 /* @internal */ 58 export const missingFileModifiedTime = new Date(0); // Any subsequent modification will occur after this time 59 60 interface Levels { 61 Low: number; 62 Medium: number; 63 High: number; 64 } 65 66 function createPollingIntervalBasedLevels(levels: Levels) { 67 return { 68 [PollingInterval.Low]: levels.Low, 69 [PollingInterval.Medium]: levels.Medium, 70 [PollingInterval.High]: levels.High 71 }; 72 } 73 74 const defaultChunkLevels: Levels = { Low: 32, Medium: 64, High: 256 }; 75 let pollingChunkSize = createPollingIntervalBasedLevels(defaultChunkLevels); 76 /* @internal */ 77 export let unchangedPollThresholds = createPollingIntervalBasedLevels(defaultChunkLevels); 78 79 /* @internal */ 80 export function setCustomPollingValues(system: System) { 81 if (!system.getEnvironmentVariable) { 82 return; 83 } 84 const pollingIntervalChanged = setCustomLevels("TSC_WATCH_POLLINGINTERVAL", PollingInterval); 85 pollingChunkSize = getCustomPollingBasedLevels("TSC_WATCH_POLLINGCHUNKSIZE", defaultChunkLevels) || pollingChunkSize; 86 unchangedPollThresholds = getCustomPollingBasedLevels("TSC_WATCH_UNCHANGEDPOLLTHRESHOLDS", defaultChunkLevels) || unchangedPollThresholds; 87 88 function getLevel(envVar: string, level: keyof Levels) { 89 return system.getEnvironmentVariable(`${envVar}_${level.toUpperCase()}`); 90 } 91 92 function getCustomLevels(baseVariable: string) { 93 let customLevels: Partial<Levels> | undefined; 94 setCustomLevel("Low"); 95 setCustomLevel("Medium"); 96 setCustomLevel("High"); 97 return customLevels; 98 99 function setCustomLevel(level: keyof Levels) { 100 const customLevel = getLevel(baseVariable, level); 101 if (customLevel) { 102 (customLevels || (customLevels = {}))[level] = Number(customLevel); 103 } 104 } 105 } 106 107 function setCustomLevels(baseVariable: string, levels: Levels) { 108 const customLevels = getCustomLevels(baseVariable); 109 if (customLevels) { 110 setLevel("Low"); 111 setLevel("Medium"); 112 setLevel("High"); 113 return true; 114 } 115 return false; 116 117 function setLevel(level: keyof Levels) { 118 levels[level] = customLevels![level] || levels[level]; 119 } 120 } 121 122 function getCustomPollingBasedLevels(baseVariable: string, defaultLevels: Levels) { 123 const customLevels = getCustomLevels(baseVariable); 124 return (pollingIntervalChanged || customLevels) && 125 createPollingIntervalBasedLevels(customLevels ? { ...defaultLevels, ...customLevels } : defaultLevels); 126 } 127 } 128 129 /* @internal */ 130 export function createDynamicPriorityPollingWatchFile(host: { 131 getModifiedTime: NonNullable<System["getModifiedTime"]>; 132 setTimeout: NonNullable<System["setTimeout"]>; 133 }): HostWatchFile { 134 interface WatchedFile extends ts.WatchedFile { 135 isClosed?: boolean; 136 unchangedPolls: number; 137 } 138 139 interface PollingIntervalQueue extends Array<WatchedFile> { 140 pollingInterval: PollingInterval; 141 pollIndex: number; 142 pollScheduled: boolean; 143 } 144 145 const watchedFiles: WatchedFile[] = []; 146 const changedFilesInLastPoll: WatchedFile[] = []; 147 const lowPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.Low); 148 const mediumPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.Medium); 149 const highPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.High); 150 return watchFile; 151 152 function watchFile(fileName: string, callback: FileWatcherCallback, defaultPollingInterval: PollingInterval): FileWatcher { 153 const file: WatchedFile = { 154 fileName, 155 callback, 156 unchangedPolls: 0, 157 mtime: getModifiedTime(fileName) 158 }; 159 watchedFiles.push(file); 160 161 addToPollingIntervalQueue(file, defaultPollingInterval); 162 return { 163 close: () => { 164 file.isClosed = true; 165 // Remove from watchedFiles 166 unorderedRemoveItem(watchedFiles, file); 167 // Do not update polling interval queue since that will happen as part of polling 168 } 169 }; 170 } 171 172 function createPollingIntervalQueue(pollingInterval: PollingInterval): PollingIntervalQueue { 173 const queue = [] as WatchedFile[] as PollingIntervalQueue; 174 queue.pollingInterval = pollingInterval; 175 queue.pollIndex = 0; 176 queue.pollScheduled = false; 177 return queue; 178 } 179 180 function pollPollingIntervalQueue(queue: PollingIntervalQueue) { 181 queue.pollIndex = pollQueue(queue, queue.pollingInterval, queue.pollIndex, pollingChunkSize[queue.pollingInterval]); 182 // Set the next polling index and timeout 183 if (queue.length) { 184 scheduleNextPoll(queue.pollingInterval); 185 } 186 else { 187 Debug.assert(queue.pollIndex === 0); 188 queue.pollScheduled = false; 189 } 190 } 191 192 function pollLowPollingIntervalQueue(queue: PollingIntervalQueue) { 193 // Always poll complete list of changedFilesInLastPoll 194 pollQueue(changedFilesInLastPoll, PollingInterval.Low, /*pollIndex*/ 0, changedFilesInLastPoll.length); 195 196 // Finally do the actual polling of the queue 197 pollPollingIntervalQueue(queue); 198 // Schedule poll if there are files in changedFilesInLastPoll but no files in the actual queue 199 // as pollPollingIntervalQueue wont schedule for next poll 200 if (!queue.pollScheduled && changedFilesInLastPoll.length) { 201 scheduleNextPoll(PollingInterval.Low); 202 } 203 } 204 205 function pollQueue(queue: (WatchedFile | undefined)[], pollingInterval: PollingInterval, pollIndex: number, chunkSize: number) { 206 // Max visit would be all elements of the queue 207 let needsVisit = queue.length; 208 let definedValueCopyToIndex = pollIndex; 209 for (let polled = 0; polled < chunkSize && needsVisit > 0; nextPollIndex(), needsVisit--) { 210 const watchedFile = queue[pollIndex]; 211 if (!watchedFile) { 212 continue; 213 } 214 else if (watchedFile.isClosed) { 215 queue[pollIndex] = undefined; 216 continue; 217 } 218 219 polled++; 220 const fileChanged = onWatchedFileStat(watchedFile, getModifiedTime(watchedFile.fileName)); 221 if (watchedFile.isClosed) { 222 // Closed watcher as part of callback 223 queue[pollIndex] = undefined; 224 } 225 else if (fileChanged) { 226 watchedFile.unchangedPolls = 0; 227 // Changed files go to changedFilesInLastPoll queue 228 if (queue !== changedFilesInLastPoll) { 229 queue[pollIndex] = undefined; 230 addChangedFileToLowPollingIntervalQueue(watchedFile); 231 } 232 } 233 else if (watchedFile.unchangedPolls !== unchangedPollThresholds[pollingInterval]) { 234 watchedFile.unchangedPolls++; 235 } 236 else if (queue === changedFilesInLastPoll) { 237 // Restart unchangedPollCount for unchanged file and move to low polling interval queue 238 watchedFile.unchangedPolls = 1; 239 queue[pollIndex] = undefined; 240 addToPollingIntervalQueue(watchedFile, PollingInterval.Low); 241 } 242 else if (pollingInterval !== PollingInterval.High) { 243 watchedFile.unchangedPolls++; 244 queue[pollIndex] = undefined; 245 addToPollingIntervalQueue(watchedFile, pollingInterval === PollingInterval.Low ? PollingInterval.Medium : PollingInterval.High); 246 } 247 248 if (queue[pollIndex]) { 249 // Copy this file to the non hole location 250 if (definedValueCopyToIndex < pollIndex) { 251 queue[definedValueCopyToIndex] = watchedFile; 252 queue[pollIndex] = undefined; 253 } 254 definedValueCopyToIndex++; 255 } 256 } 257 258 // Return next poll index 259 return pollIndex; 260 261 function nextPollIndex() { 262 pollIndex++; 263 if (pollIndex === queue.length) { 264 if (definedValueCopyToIndex < pollIndex) { 265 // There are holes from nextDefinedValueIndex to end of queue, change queue size 266 queue.length = definedValueCopyToIndex; 267 } 268 pollIndex = 0; 269 definedValueCopyToIndex = 0; 270 } 271 } 272 } 273 274 function pollingIntervalQueue(pollingInterval: PollingInterval) { 275 switch (pollingInterval) { 276 case PollingInterval.Low: 277 return lowPollingIntervalQueue; 278 case PollingInterval.Medium: 279 return mediumPollingIntervalQueue; 280 case PollingInterval.High: 281 return highPollingIntervalQueue; 282 } 283 } 284 285 function addToPollingIntervalQueue(file: WatchedFile, pollingInterval: PollingInterval) { 286 pollingIntervalQueue(pollingInterval).push(file); 287 scheduleNextPollIfNotAlreadyScheduled(pollingInterval); 288 } 289 290 function addChangedFileToLowPollingIntervalQueue(file: WatchedFile) { 291 changedFilesInLastPoll.push(file); 292 scheduleNextPollIfNotAlreadyScheduled(PollingInterval.Low); 293 } 294 295 function scheduleNextPollIfNotAlreadyScheduled(pollingInterval: PollingInterval) { 296 if (!pollingIntervalQueue(pollingInterval).pollScheduled) { 297 scheduleNextPoll(pollingInterval); 298 } 299 } 300 301 function scheduleNextPoll(pollingInterval: PollingInterval) { 302 pollingIntervalQueue(pollingInterval).pollScheduled = host.setTimeout(pollingInterval === PollingInterval.Low ? pollLowPollingIntervalQueue : pollPollingIntervalQueue, pollingInterval, pollingIntervalQueue(pollingInterval)); 303 } 304 305 function getModifiedTime(fileName: string) { 306 return host.getModifiedTime(fileName) || missingFileModifiedTime; 307 } 308 } 309 310 function createUseFsEventsOnParentDirectoryWatchFile(fsWatch: FsWatch, useCaseSensitiveFileNames: boolean): HostWatchFile { 311 // One file can have multiple watchers 312 const fileWatcherCallbacks = createMultiMap<FileWatcherCallback>(); 313 const dirWatchers = new Map<string, DirectoryWatcher>(); 314 const toCanonicalName = createGetCanonicalFileName(useCaseSensitiveFileNames); 315 return nonPollingWatchFile; 316 317 function nonPollingWatchFile(fileName: string, callback: FileWatcherCallback, _pollingInterval: PollingInterval, fallbackOptions: WatchOptions | undefined): FileWatcher { 318 const filePath = toCanonicalName(fileName); 319 fileWatcherCallbacks.add(filePath, callback); 320 const dirPath = getDirectoryPath(filePath) || "."; 321 const watcher = dirWatchers.get(dirPath) || 322 createDirectoryWatcher(getDirectoryPath(fileName) || ".", dirPath, fallbackOptions); 323 watcher.referenceCount++; 324 return { 325 close: () => { 326 if (watcher.referenceCount === 1) { 327 watcher.close(); 328 dirWatchers.delete(dirPath); 329 } 330 else { 331 watcher.referenceCount--; 332 } 333 fileWatcherCallbacks.remove(filePath, callback); 334 } 335 }; 336 } 337 338 function createDirectoryWatcher(dirName: string, dirPath: string, fallbackOptions: WatchOptions | undefined) { 339 const watcher = fsWatch( 340 dirName, 341 FileSystemEntryKind.Directory, 342 (_eventName: string, relativeFileName) => { 343 // When files are deleted from disk, the triggered "rename" event would have a relativefileName of "undefined" 344 if (!isString(relativeFileName)) { return; } 345 const fileName = getNormalizedAbsolutePath(relativeFileName, dirName); 346 // Some applications save a working file via rename operations 347 const callbacks = fileName && fileWatcherCallbacks.get(toCanonicalName(fileName)); 348 if (callbacks) { 349 for (const fileCallback of callbacks) { 350 fileCallback(fileName, FileWatcherEventKind.Changed); 351 } 352 } 353 }, 354 /*recursive*/ false, 355 PollingInterval.Medium, 356 fallbackOptions 357 ) as DirectoryWatcher; 358 watcher.referenceCount = 0; 359 dirWatchers.set(dirPath, watcher); 360 return watcher; 361 } 362 } 363 364 /* @internal */ 365 export function createSingleFileWatcherPerName( 366 watchFile: HostWatchFile, 367 useCaseSensitiveFileNames: boolean 368 ): HostWatchFile { 369 interface SingleFileWatcher { 370 watcher: FileWatcher; 371 refCount: number; 372 } 373 const cache = new Map<string, SingleFileWatcher>(); 374 const callbacksCache = createMultiMap<FileWatcherCallback>(); 375 const toCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); 376 377 return (fileName, callback, pollingInterval, options) => { 378 const path = toCanonicalFileName(fileName); 379 const existing = cache.get(path); 380 if (existing) { 381 existing.refCount++; 382 } 383 else { 384 cache.set(path, { 385 watcher: watchFile( 386 fileName, 387 (fileName, eventKind) => forEach( 388 callbacksCache.get(path), 389 cb => cb(fileName, eventKind) 390 ), 391 pollingInterval, 392 options 393 ), 394 refCount: 1 395 }); 396 } 397 callbacksCache.add(path, callback); 398 399 return { 400 close: () => { 401 const watcher = Debug.checkDefined(cache.get(path)); 402 callbacksCache.remove(path, callback); 403 watcher.refCount--; 404 if (watcher.refCount) return; 405 cache.delete(path); 406 closeFileWatcherOf(watcher); 407 } 408 }; 409 }; 410 } 411 412 /** 413 * Returns true if file status changed 414 */ 415 /*@internal*/ 416 export function onWatchedFileStat(watchedFile: WatchedFile, modifiedTime: Date): boolean { 417 const oldTime = watchedFile.mtime.getTime(); 418 const newTime = modifiedTime.getTime(); 419 if (oldTime !== newTime) { 420 watchedFile.mtime = modifiedTime; 421 watchedFile.callback(watchedFile.fileName, getFileWatcherEventKind(oldTime, newTime)); 422 return true; 423 } 424 425 return false; 426 } 427 428 /*@internal*/ 429 export function getFileWatcherEventKind(oldTime: number, newTime: number) { 430 return oldTime === 0 431 ? FileWatcherEventKind.Created 432 : newTime === 0 433 ? FileWatcherEventKind.Deleted 434 : FileWatcherEventKind.Changed; 435 } 436 437 /*@internal*/ 438 export const ignoredPaths = ["/node_modules/.", "/.git", "/.#"]; 439 440 /*@internal*/ 441 export let sysLog: (s: string) => void = noop; // eslint-disable-line prefer-const 442 443 /*@internal*/ 444 export function setSysLog(logger: typeof sysLog) { 445 sysLog = logger; 446 } 447 448 /*@internal*/ 449 export interface RecursiveDirectoryWatcherHost { 450 watchDirectory: HostWatchDirectory; 451 useCaseSensitiveFileNames: boolean; 452 getCurrentDirectory: System["getCurrentDirectory"]; 453 getAccessibleSortedChildDirectories(path: string): readonly string[]; 454 directoryExists(dir: string): boolean; 455 realpath(s: string): string; 456 setTimeout: NonNullable<System["setTimeout"]>; 457 clearTimeout: NonNullable<System["clearTimeout"]>; 458 } 459 460 /** 461 * Watch the directory recursively using host provided method to watch child directories 462 * that means if this is recursive watcher, watch the children directories as well 463 * (eg on OS that dont support recursive watch using fs.watch use fs.watchFile) 464 */ 465 /*@internal*/ 466 export function createDirectoryWatcherSupportingRecursive({ 467 watchDirectory, 468 useCaseSensitiveFileNames, 469 getCurrentDirectory, 470 getAccessibleSortedChildDirectories, 471 directoryExists, 472 realpath, 473 setTimeout, 474 clearTimeout 475 }: RecursiveDirectoryWatcherHost): HostWatchDirectory { 476 interface ChildDirectoryWatcher extends FileWatcher { 477 dirName: string; 478 } 479 type ChildWatches = readonly ChildDirectoryWatcher[]; 480 interface HostDirectoryWatcher { 481 watcher: FileWatcher; 482 childWatches: ChildWatches; 483 refCount: number; 484 } 485 486 const cache = new Map<string, HostDirectoryWatcher>(); 487 const callbackCache = createMultiMap<Path, { dirName: string; callback: DirectoryWatcherCallback; }>(); 488 const cacheToUpdateChildWatches = new Map<Path, { dirName: string; options: WatchOptions | undefined; fileNames: string[]; }>(); 489 let timerToUpdateChildWatches: any; 490 491 const filePathComparer = getStringComparer(!useCaseSensitiveFileNames); 492 const toCanonicalFilePath = createGetCanonicalFileName(useCaseSensitiveFileNames); 493 494 return (dirName, callback, recursive, options) => recursive ? 495 createDirectoryWatcher(dirName, options, callback) : 496 watchDirectory(dirName, callback, recursive, options); 497 498 /** 499 * Create the directory watcher for the dirPath. 500 */ 501 function createDirectoryWatcher(dirName: string, options: WatchOptions | undefined, callback?: DirectoryWatcherCallback): ChildDirectoryWatcher { 502 const dirPath = toCanonicalFilePath(dirName) as Path; 503 let directoryWatcher = cache.get(dirPath); 504 if (directoryWatcher) { 505 directoryWatcher.refCount++; 506 } 507 else { 508 directoryWatcher = { 509 watcher: watchDirectory(dirName, fileName => { 510 if (isIgnoredPath(fileName, options)) return; 511 512 if (options?.synchronousWatchDirectory) { 513 // Call the actual callback 514 invokeCallbacks(dirPath, fileName); 515 516 // Iterate through existing children and update the watches if needed 517 updateChildWatches(dirName, dirPath, options); 518 } 519 else { 520 nonSyncUpdateChildWatches(dirName, dirPath, fileName, options); 521 } 522 }, /*recursive*/ false, options), 523 refCount: 1, 524 childWatches: emptyArray 525 }; 526 cache.set(dirPath, directoryWatcher); 527 updateChildWatches(dirName, dirPath, options); 528 } 529 530 const callbackToAdd = callback && { dirName, callback }; 531 if (callbackToAdd) { 532 callbackCache.add(dirPath, callbackToAdd); 533 } 534 535 return { 536 dirName, 537 close: () => { 538 const directoryWatcher = Debug.checkDefined(cache.get(dirPath)); 539 if (callbackToAdd) callbackCache.remove(dirPath, callbackToAdd); 540 directoryWatcher.refCount--; 541 542 if (directoryWatcher.refCount) return; 543 544 cache.delete(dirPath); 545 closeFileWatcherOf(directoryWatcher); 546 directoryWatcher.childWatches.forEach(closeFileWatcher); 547 } 548 }; 549 } 550 551 type InvokeMap = ESMap<Path, string[] | true>; 552 function invokeCallbacks(dirPath: Path, fileName: string): void; 553 function invokeCallbacks(dirPath: Path, invokeMap: InvokeMap, fileNames: string[] | undefined): void; 554 function invokeCallbacks(dirPath: Path, fileNameOrInvokeMap: string | InvokeMap, fileNames?: string[]) { 555 let fileName: string | undefined; 556 let invokeMap: InvokeMap | undefined; 557 if (isString(fileNameOrInvokeMap)) { 558 fileName = fileNameOrInvokeMap; 559 } 560 else { 561 invokeMap = fileNameOrInvokeMap; 562 } 563 // Call the actual callback 564 callbackCache.forEach((callbacks, rootDirName) => { 565 if (invokeMap && invokeMap.get(rootDirName) === true) return; 566 if (rootDirName === dirPath || (startsWith(dirPath, rootDirName) && dirPath[rootDirName.length] === directorySeparator)) { 567 if (invokeMap) { 568 if (fileNames) { 569 const existing = invokeMap.get(rootDirName); 570 if (existing) { 571 (existing as string[]).push(...fileNames); 572 } 573 else { 574 invokeMap.set(rootDirName, fileNames.slice()); 575 } 576 } 577 else { 578 invokeMap.set(rootDirName, true); 579 } 580 } 581 else { 582 callbacks.forEach(({ callback }) => callback(fileName!)); 583 } 584 } 585 }); 586 } 587 588 function nonSyncUpdateChildWatches(dirName: string, dirPath: Path, fileName: string, options: WatchOptions | undefined) { 589 // Iterate through existing children and update the watches if needed 590 const parentWatcher = cache.get(dirPath); 591 if (parentWatcher && directoryExists(dirName)) { 592 // Schedule the update and postpone invoke for callbacks 593 scheduleUpdateChildWatches(dirName, dirPath, fileName, options); 594 return; 595 } 596 597 // Call the actual callbacks and remove child watches 598 invokeCallbacks(dirPath, fileName); 599 removeChildWatches(parentWatcher); 600 } 601 602 function scheduleUpdateChildWatches(dirName: string, dirPath: Path, fileName: string, options: WatchOptions | undefined) { 603 const existing = cacheToUpdateChildWatches.get(dirPath); 604 if (existing) { 605 existing.fileNames.push(fileName); 606 } 607 else { 608 cacheToUpdateChildWatches.set(dirPath, { dirName, options, fileNames: [fileName] }); 609 } 610 if (timerToUpdateChildWatches) { 611 clearTimeout(timerToUpdateChildWatches); 612 timerToUpdateChildWatches = undefined; 613 } 614 timerToUpdateChildWatches = setTimeout(onTimerToUpdateChildWatches, 1000); 615 } 616 617 function onTimerToUpdateChildWatches() { 618 timerToUpdateChildWatches = undefined; 619 sysLog(`sysLog:: onTimerToUpdateChildWatches:: ${cacheToUpdateChildWatches.size}`); 620 const start = timestamp(); 621 const invokeMap = new Map<Path, string[]>(); 622 623 while (!timerToUpdateChildWatches && cacheToUpdateChildWatches.size) { 624 const result = cacheToUpdateChildWatches.entries().next(); 625 Debug.assert(!result.done); 626 const { value: [dirPath, { dirName, options, fileNames }] } = result; 627 cacheToUpdateChildWatches.delete(dirPath); 628 // Because the child refresh is fresh, we would need to invalidate whole root directory being watched 629 // to ensure that all the changes are reflected at this time 630 const hasChanges = updateChildWatches(dirName, dirPath, options); 631 invokeCallbacks(dirPath, invokeMap, hasChanges ? undefined : fileNames); 632 } 633 634 sysLog(`sysLog:: invokingWatchers:: Elapsed:: ${timestamp() - start}ms:: ${cacheToUpdateChildWatches.size}`); 635 callbackCache.forEach((callbacks, rootDirName) => { 636 const existing = invokeMap.get(rootDirName); 637 if (existing) { 638 callbacks.forEach(({ callback, dirName }) => { 639 if (isArray(existing)) { 640 existing.forEach(callback); 641 } 642 else { 643 callback(dirName); 644 } 645 }); 646 } 647 }); 648 649 const elapsed = timestamp() - start; 650 sysLog(`sysLog:: Elapsed:: ${elapsed}ms:: onTimerToUpdateChildWatches:: ${cacheToUpdateChildWatches.size} ${timerToUpdateChildWatches}`); 651 } 652 653 function removeChildWatches(parentWatcher: HostDirectoryWatcher | undefined) { 654 if (!parentWatcher) return; 655 const existingChildWatches = parentWatcher.childWatches; 656 parentWatcher.childWatches = emptyArray; 657 for (const childWatcher of existingChildWatches) { 658 childWatcher.close(); 659 removeChildWatches(cache.get(toCanonicalFilePath(childWatcher.dirName))); 660 } 661 } 662 663 function updateChildWatches(parentDir: string, parentDirPath: Path, options: WatchOptions | undefined) { 664 // Iterate through existing children and update the watches if needed 665 const parentWatcher = cache.get(parentDirPath); 666 if (!parentWatcher) return false; 667 let newChildWatches: ChildDirectoryWatcher[] | undefined; 668 const hasChanges = enumerateInsertsAndDeletes<string, ChildDirectoryWatcher>( 669 directoryExists(parentDir) ? mapDefined(getAccessibleSortedChildDirectories(parentDir), child => { 670 const childFullName = getNormalizedAbsolutePath(child, parentDir); 671 // Filter our the symbolic link directories since those arent included in recursive watch 672 // which is same behaviour when recursive: true is passed to fs.watch 673 return !isIgnoredPath(childFullName, options) && filePathComparer(childFullName, normalizePath(realpath(childFullName))) === Comparison.EqualTo ? childFullName : undefined; 674 }) : emptyArray, 675 parentWatcher.childWatches, 676 (child, childWatcher) => filePathComparer(child, childWatcher.dirName), 677 createAndAddChildDirectoryWatcher, 678 closeFileWatcher, 679 addChildDirectoryWatcher 680 ); 681 parentWatcher.childWatches = newChildWatches || emptyArray; 682 return hasChanges; 683 684 /** 685 * Create new childDirectoryWatcher and add it to the new ChildDirectoryWatcher list 686 */ 687 function createAndAddChildDirectoryWatcher(childName: string) { 688 const result = createDirectoryWatcher(childName, options); 689 addChildDirectoryWatcher(result); 690 } 691 692 /** 693 * Add child directory watcher to the new ChildDirectoryWatcher list 694 */ 695 function addChildDirectoryWatcher(childWatcher: ChildDirectoryWatcher) { 696 (newChildWatches || (newChildWatches = [])).push(childWatcher); 697 } 698 } 699 700 function isIgnoredPath(path: string, options: WatchOptions | undefined) { 701 return some(ignoredPaths, searchPath => isInPath(path, searchPath)) || 702 isIgnoredByWatchOptions(path, options, useCaseSensitiveFileNames, getCurrentDirectory); 703 } 704 705 function isInPath(path: string, searchPath: string) { 706 if (stringContains(path, searchPath)) return true; 707 if (useCaseSensitiveFileNames) return false; 708 return stringContains(toCanonicalFilePath(path), searchPath); 709 } 710 } 711 712 /*@internal*/ 713 export type FsWatchCallback = (eventName: "rename" | "change", relativeFileName: string | undefined) => void; 714 /*@internal*/ 715 export type FsWatch = (fileOrDirectory: string, entryKind: FileSystemEntryKind, callback: FsWatchCallback, recursive: boolean, fallbackPollingInterval: PollingInterval, fallbackOptions: WatchOptions | undefined) => FileWatcher; 716 717 /*@internal*/ 718 export const enum FileSystemEntryKind { 719 File, 720 Directory, 721 } 722 723 /*@internal*/ 724 export function createFileWatcherCallback(callback: FsWatchCallback): FileWatcherCallback { 725 return (_fileName, eventKind) => callback(eventKind === FileWatcherEventKind.Changed ? "change" : "rename", ""); 726 } 727 728 function createFsWatchCallbackForFileWatcherCallback( 729 fileName: string, 730 callback: FileWatcherCallback, 731 fileExists: System["fileExists"] 732 ): FsWatchCallback { 733 return eventName => { 734 if (eventName === "rename") { 735 callback(fileName, fileExists(fileName) ? FileWatcherEventKind.Created : FileWatcherEventKind.Deleted); 736 } 737 else { 738 // Change 739 callback(fileName, FileWatcherEventKind.Changed); 740 } 741 }; 742 } 743 744 function isIgnoredByWatchOptions( 745 pathToCheck: string, 746 options: WatchOptions | undefined, 747 useCaseSensitiveFileNames: boolean, 748 getCurrentDirectory: System["getCurrentDirectory"], 749 ) { 750 return (options?.excludeDirectories || options?.excludeFiles) && ( 751 matchesExclude(pathToCheck, options?.excludeFiles, useCaseSensitiveFileNames, getCurrentDirectory()) || 752 matchesExclude(pathToCheck, options?.excludeDirectories, useCaseSensitiveFileNames, getCurrentDirectory()) 753 ); 754 } 755 756 function createFsWatchCallbackForDirectoryWatcherCallback( 757 directoryName: string, 758 callback: DirectoryWatcherCallback, 759 options: WatchOptions | undefined, 760 useCaseSensitiveFileNames: boolean, 761 getCurrentDirectory: System["getCurrentDirectory"], 762 ): FsWatchCallback { 763 return (eventName, relativeFileName) => { 764 // In watchDirectory we only care about adding and removing files (when event name is 765 // "rename"); changes made within files are handled by corresponding fileWatchers (when 766 // event name is "change") 767 if (eventName === "rename") { 768 // When deleting a file, the passed baseFileName is null 769 const fileName = !relativeFileName ? directoryName : normalizePath(combinePaths(directoryName, relativeFileName)); 770 if (!relativeFileName || !isIgnoredByWatchOptions(fileName, options, useCaseSensitiveFileNames, getCurrentDirectory)) { 771 callback(fileName); 772 } 773 } 774 }; 775 } 776 777 /*@internal*/ 778 export interface CreateSystemWatchFunctions { 779 // Polling watch file 780 pollingWatchFile: HostWatchFile; 781 // For dynamic polling watch file 782 getModifiedTime: NonNullable<System["getModifiedTime"]>; 783 setTimeout: NonNullable<System["setTimeout"]>; 784 clearTimeout: NonNullable<System["clearTimeout"]>; 785 // For fs events : 786 fsWatch: FsWatch; 787 fileExists: System["fileExists"]; 788 useCaseSensitiveFileNames: boolean; 789 getCurrentDirectory: System["getCurrentDirectory"]; 790 fsSupportsRecursiveFsWatch: boolean; 791 directoryExists: System["directoryExists"]; 792 getAccessibleSortedChildDirectories(path: string): readonly string[]; 793 realpath(s: string): string; 794 // For backward compatibility environment variables 795 tscWatchFile: string | undefined; 796 useNonPollingWatchers?: boolean; 797 tscWatchDirectory: string | undefined; 798 } 799 800 /*@internal*/ 801 export function createSystemWatchFunctions({ 802 pollingWatchFile, 803 getModifiedTime, 804 setTimeout, 805 clearTimeout, 806 fsWatch, 807 fileExists, 808 useCaseSensitiveFileNames, 809 getCurrentDirectory, 810 fsSupportsRecursiveFsWatch, 811 directoryExists, 812 getAccessibleSortedChildDirectories, 813 realpath, 814 tscWatchFile, 815 useNonPollingWatchers, 816 tscWatchDirectory, 817 }: CreateSystemWatchFunctions): { watchFile: HostWatchFile; watchDirectory: HostWatchDirectory; } { 818 let dynamicPollingWatchFile: HostWatchFile | undefined; 819 let nonPollingWatchFile: HostWatchFile | undefined; 820 let hostRecursiveDirectoryWatcher: HostWatchDirectory | undefined; 821 return { 822 watchFile, 823 watchDirectory 824 }; 825 826 function watchFile(fileName: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined): FileWatcher { 827 options = updateOptionsForWatchFile(options, useNonPollingWatchers); 828 const watchFileKind = Debug.checkDefined(options.watchFile); 829 switch (watchFileKind) { 830 case WatchFileKind.FixedPollingInterval: 831 return pollingWatchFile(fileName, callback, PollingInterval.Low, /*options*/ undefined); 832 case WatchFileKind.PriorityPollingInterval: 833 return pollingWatchFile(fileName, callback, pollingInterval, /*options*/ undefined); 834 case WatchFileKind.DynamicPriorityPolling: 835 return ensureDynamicPollingWatchFile()(fileName, callback, pollingInterval, /*options*/ undefined); 836 case WatchFileKind.UseFsEvents: 837 return fsWatch( 838 fileName, 839 FileSystemEntryKind.File, 840 createFsWatchCallbackForFileWatcherCallback(fileName, callback, fileExists), 841 /*recursive*/ false, 842 pollingInterval, 843 getFallbackOptions(options) 844 ); 845 case WatchFileKind.UseFsEventsOnParentDirectory: 846 if (!nonPollingWatchFile) { 847 nonPollingWatchFile = createUseFsEventsOnParentDirectoryWatchFile(fsWatch, useCaseSensitiveFileNames); 848 } 849 return nonPollingWatchFile(fileName, callback, pollingInterval, getFallbackOptions(options)); 850 default: 851 Debug.assertNever(watchFileKind); 852 } 853 } 854 855 function ensureDynamicPollingWatchFile() { 856 return dynamicPollingWatchFile || 857 (dynamicPollingWatchFile = createDynamicPriorityPollingWatchFile({ getModifiedTime, setTimeout })); 858 } 859 860 function updateOptionsForWatchFile(options: WatchOptions | undefined, useNonPollingWatchers?: boolean): WatchOptions { 861 if (options && options.watchFile !== undefined) return options; 862 switch (tscWatchFile) { 863 case "PriorityPollingInterval": 864 // Use polling interval based on priority when create watch using host.watchFile 865 return { watchFile: WatchFileKind.PriorityPollingInterval }; 866 case "DynamicPriorityPolling": 867 // Use polling interval but change the interval depending on file changes and their default polling interval 868 return { watchFile: WatchFileKind.DynamicPriorityPolling }; 869 case "UseFsEvents": 870 // Use notifications from FS to watch with falling back to fs.watchFile 871 return generateWatchFileOptions(WatchFileKind.UseFsEvents, PollingWatchKind.PriorityInterval, options); 872 case "UseFsEventsWithFallbackDynamicPolling": 873 // Use notifications from FS to watch with falling back to dynamic watch file 874 return generateWatchFileOptions(WatchFileKind.UseFsEvents, PollingWatchKind.DynamicPriority, options); 875 case "UseFsEventsOnParentDirectory": 876 useNonPollingWatchers = true; 877 // fall through 878 default: 879 return useNonPollingWatchers ? 880 // Use notifications from FS to watch with falling back to fs.watchFile 881 generateWatchFileOptions(WatchFileKind.UseFsEventsOnParentDirectory, PollingWatchKind.PriorityInterval, options) : 882 // Default to do not use fixed polling interval 883 { watchFile: WatchFileKind.FixedPollingInterval }; 884 } 885 } 886 887 function generateWatchFileOptions( 888 watchFile: WatchFileKind, 889 fallbackPolling: PollingWatchKind, 890 options: WatchOptions | undefined 891 ): WatchOptions { 892 const defaultFallbackPolling = options?.fallbackPolling; 893 return { 894 watchFile, 895 fallbackPolling: defaultFallbackPolling === undefined ? 896 fallbackPolling : 897 defaultFallbackPolling 898 }; 899 } 900 901 function watchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined): FileWatcher { 902 if (fsSupportsRecursiveFsWatch) { 903 return fsWatch( 904 directoryName, 905 FileSystemEntryKind.Directory, 906 createFsWatchCallbackForDirectoryWatcherCallback(directoryName, callback, options, useCaseSensitiveFileNames, getCurrentDirectory), 907 recursive, 908 PollingInterval.Medium, 909 getFallbackOptions(options) 910 ); 911 } 912 913 if (!hostRecursiveDirectoryWatcher) { 914 hostRecursiveDirectoryWatcher = createDirectoryWatcherSupportingRecursive({ 915 useCaseSensitiveFileNames, 916 getCurrentDirectory, 917 directoryExists, 918 getAccessibleSortedChildDirectories, 919 watchDirectory: nonRecursiveWatchDirectory, 920 realpath, 921 setTimeout, 922 clearTimeout 923 }); 924 } 925 return hostRecursiveDirectoryWatcher(directoryName, callback, recursive, options); 926 } 927 928 function nonRecursiveWatchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined): FileWatcher { 929 Debug.assert(!recursive); 930 const watchDirectoryOptions = updateOptionsForWatchDirectory(options); 931 const watchDirectoryKind = Debug.checkDefined(watchDirectoryOptions.watchDirectory); 932 switch (watchDirectoryKind) { 933 case WatchDirectoryKind.FixedPollingInterval: 934 return pollingWatchFile( 935 directoryName, 936 () => callback(directoryName), 937 PollingInterval.Medium, 938 /*options*/ undefined 939 ); 940 case WatchDirectoryKind.DynamicPriorityPolling: 941 return ensureDynamicPollingWatchFile()( 942 directoryName, 943 () => callback(directoryName), 944 PollingInterval.Medium, 945 /*options*/ undefined 946 ); 947 case WatchDirectoryKind.UseFsEvents: 948 return fsWatch( 949 directoryName, 950 FileSystemEntryKind.Directory, 951 createFsWatchCallbackForDirectoryWatcherCallback(directoryName, callback, options, useCaseSensitiveFileNames, getCurrentDirectory), 952 recursive, 953 PollingInterval.Medium, 954 getFallbackOptions(watchDirectoryOptions) 955 ); 956 default: 957 Debug.assertNever(watchDirectoryKind); 958 } 959 } 960 961 function updateOptionsForWatchDirectory(options: WatchOptions | undefined): WatchOptions { 962 if (options && options.watchDirectory !== undefined) return options; 963 switch (tscWatchDirectory) { 964 case "RecursiveDirectoryUsingFsWatchFile": 965 // Use polling interval based on priority when create watch using host.watchFile 966 return { watchDirectory: WatchDirectoryKind.FixedPollingInterval }; 967 case "RecursiveDirectoryUsingDynamicPriorityPolling": 968 // Use polling interval but change the interval depending on file changes and their default polling interval 969 return { watchDirectory: WatchDirectoryKind.DynamicPriorityPolling }; 970 default: 971 const defaultFallbackPolling = options?.fallbackPolling; 972 return { 973 watchDirectory: WatchDirectoryKind.UseFsEvents, 974 fallbackPolling: defaultFallbackPolling !== undefined ? 975 defaultFallbackPolling : 976 undefined 977 }; 978 } 979 } 980 } 981 982 /** 983 * patch writefile to create folder before writing the file 984 */ 985 /*@internal*/ 986 export function patchWriteFileEnsuringDirectory(sys: System) { 987 // patch writefile to create folder before writing the file 988 const originalWriteFile = sys.writeFile; 989 sys.writeFile = (path, data, writeBom) => 990 writeFileEnsuringDirectories( 991 path, 992 data, 993 !!writeBom, 994 (path, data, writeByteOrderMark) => originalWriteFile.call(sys, path, data, writeByteOrderMark), 995 path => sys.createDirectory(path), 996 path => sys.directoryExists(path)); 997 } 998 999 /*@internal*/ 1000 export type BufferEncoding = "ascii" | "utf8" | "utf-8" | "utf16le" | "ucs2" | "ucs-2" | "base64" | "latin1" | "binary" | "hex"; 1001 1002 /*@internal*/ 1003 interface NodeBuffer extends Uint8Array { 1004 constructor: any; 1005 write(str: string, encoding?: BufferEncoding): number; 1006 write(str: string, offset: number, encoding?: BufferEncoding): number; 1007 write(str: string, offset: number, length: number, encoding?: BufferEncoding): number; 1008 toString(encoding?: string, start?: number, end?: number): string; 1009 toJSON(): { type: "Buffer"; data: number[] }; 1010 equals(otherBuffer: Uint8Array): boolean; 1011 compare( 1012 otherBuffer: Uint8Array, 1013 targetStart?: number, 1014 targetEnd?: number, 1015 sourceStart?: number, 1016 sourceEnd?: number 1017 ): number; 1018 copy(targetBuffer: Uint8Array, targetStart?: number, sourceStart?: number, sourceEnd?: number): number; 1019 slice(begin?: number, end?: number): Buffer; 1020 subarray(begin?: number, end?: number): Buffer; 1021 writeUIntLE(value: number, offset: number, byteLength: number): number; 1022 writeUIntBE(value: number, offset: number, byteLength: number): number; 1023 writeIntLE(value: number, offset: number, byteLength: number): number; 1024 writeIntBE(value: number, offset: number, byteLength: number): number; 1025 readUIntLE(offset: number, byteLength: number): number; 1026 readUIntBE(offset: number, byteLength: number): number; 1027 readIntLE(offset: number, byteLength: number): number; 1028 readIntBE(offset: number, byteLength: number): number; 1029 readUInt8(offset: number): number; 1030 readUInt16LE(offset: number): number; 1031 readUInt16BE(offset: number): number; 1032 readUInt32LE(offset: number): number; 1033 readUInt32BE(offset: number): number; 1034 readInt8(offset: number): number; 1035 readInt16LE(offset: number): number; 1036 readInt16BE(offset: number): number; 1037 readInt32LE(offset: number): number; 1038 readInt32BE(offset: number): number; 1039 readFloatLE(offset: number): number; 1040 readFloatBE(offset: number): number; 1041 readDoubleLE(offset: number): number; 1042 readDoubleBE(offset: number): number; 1043 reverse(): this; 1044 swap16(): Buffer; 1045 swap32(): Buffer; 1046 swap64(): Buffer; 1047 writeUInt8(value: number, offset: number): number; 1048 writeUInt16LE(value: number, offset: number): number; 1049 writeUInt16BE(value: number, offset: number): number; 1050 writeUInt32LE(value: number, offset: number): number; 1051 writeUInt32BE(value: number, offset: number): number; 1052 writeInt8(value: number, offset: number): number; 1053 writeInt16LE(value: number, offset: number): number; 1054 writeInt16BE(value: number, offset: number): number; 1055 writeInt32LE(value: number, offset: number): number; 1056 writeInt32BE(value: number, offset: number): number; 1057 writeFloatLE(value: number, offset: number): number; 1058 writeFloatBE(value: number, offset: number): number; 1059 writeDoubleLE(value: number, offset: number): number; 1060 writeDoubleBE(value: number, offset: number): number; 1061 readBigUInt64BE?(offset?: number): bigint; 1062 readBigUInt64LE?(offset?: number): bigint; 1063 readBigInt64BE?(offset?: number): bigint; 1064 readBigInt64LE?(offset?: number): bigint; 1065 writeBigInt64BE?(value: bigint, offset?: number): number; 1066 writeBigInt64LE?(value: bigint, offset?: number): number; 1067 writeBigUInt64BE?(value: bigint, offset?: number): number; 1068 writeBigUInt64LE?(value: bigint, offset?: number): number; 1069 fill(value: string | Uint8Array | number, offset?: number, end?: number, encoding?: BufferEncoding): this; 1070 indexOf(value: string | number | Uint8Array, byteOffset?: number, encoding?: BufferEncoding): number; 1071 lastIndexOf(value: string | number | Uint8Array, byteOffset?: number, encoding?: BufferEncoding): number; 1072 entries(): IterableIterator<[number, number]>; 1073 includes(value: string | number | Buffer, byteOffset?: number, encoding?: BufferEncoding): boolean; 1074 keys(): IterableIterator<number>; 1075 values(): IterableIterator<number>; 1076 } 1077 1078 /*@internal*/ 1079 interface Buffer extends NodeBuffer { } 1080 1081 // TODO: GH#18217 Methods on System are often used as if they are certainly defined 1082 export interface System { 1083 args: string[]; 1084 newLine: string; 1085 useCaseSensitiveFileNames: boolean; 1086 write(s: string): void; 1087 writeOutputIsTTY?(): boolean; 1088 readFile(path: string, encoding?: string): string | undefined; 1089 getFileSize?(path: string): number; 1090 writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; 1091 1092 /** 1093 * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that 1094 * use native OS file watching 1095 */ 1096 watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: WatchOptions): FileWatcher; 1097 watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: WatchOptions): FileWatcher; 1098 resolvePath(path: string): string; 1099 fileExists(path: string): boolean; 1100 directoryExists(path: string): boolean; 1101 createDirectory(path: string): void; 1102 getExecutingFilePath(): string; 1103 getCurrentDirectory(): string; 1104 getDirectories(path: string): string[]; 1105 readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[]; 1106 getModifiedTime?(path: string): Date | undefined; 1107 setModifiedTime?(path: string, time: Date): void; 1108 deleteFile?(path: string): void; 1109 /** 1110 * A good implementation is node.js' `crypto.createHash`. (https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm) 1111 */ 1112 createHash?(data: string): string; 1113 /** This must be cryptographically secure. Only implement this method using `crypto.createHash("sha256")`. */ 1114 createSHA256Hash?(data: string): string; 1115 getMemoryUsage?(): number; 1116 exit(exitCode?: number): void; 1117 /*@internal*/ enableCPUProfiler?(path: string, continuation: () => void): boolean; 1118 /*@internal*/ disableCPUProfiler?(continuation: () => void): boolean; 1119 /*@internal*/ cpuProfilingEnabled?(): boolean; 1120 realpath?(path: string): string; 1121 /*@internal*/ getEnvironmentVariable(name: string): string; 1122 /*@internal*/ tryEnableSourceMapsForHost?(): void; 1123 /*@internal*/ debugMode?: boolean; 1124 setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; 1125 clearTimeout?(timeoutId: any): void; 1126 clearScreen?(): void; 1127 /*@internal*/ setBlocking?(): void; 1128 base64decode?(input: string): string; 1129 base64encode?(input: string): string; 1130 /*@internal*/ bufferFrom?(input: string, encoding?: string): Buffer; 1131 // For testing 1132 /*@internal*/ now?(): Date; 1133 /*@internal*/ require?(baseDir: string, moduleName: string): RequireResult; 1134 } 1135 1136 export interface FileWatcher { 1137 close(): void; 1138 } 1139 1140 interface DirectoryWatcher extends FileWatcher { 1141 referenceCount: number; 1142 } 1143 1144 declare const require: any; 1145 declare const process: any; 1146 declare const global: any; 1147 declare const __filename: string; 1148 declare const __dirname: string; 1149 1150 export function getNodeMajorVersion(): number | undefined { 1151 if (typeof process === "undefined") { 1152 return undefined; 1153 } 1154 const version: string = process.version; 1155 if (!version) { 1156 return undefined; 1157 } 1158 const dot = version.indexOf("."); 1159 if (dot === -1) { 1160 return undefined; 1161 } 1162 return parseInt(version.substring(1, dot)); 1163 } 1164 1165 // TODO: GH#18217 this is used as if it's certainly defined in many places. 1166 // eslint-disable-next-line prefer-const 1167 export let sys: System = (() => { 1168 // NodeJS detects "\uFEFF" at the start of the string and *replaces* it with the actual 1169 // byte order mark from the specified encoding. Using any other byte order mark does 1170 // not actually work. 1171 const byteOrderMarkIndicator = "\uFEFF"; 1172 1173 function getNodeSystem(): System { 1174 const nativePattern = /^native |^\([^)]+\)$|^(internal[\\/]|[a-zA-Z0-9_\s]+(\.js)?$)/; 1175 const _fs: typeof import("fs") = require("fs"); 1176 const _path: typeof import("path") = require("path"); 1177 const _os = require("os"); 1178 // crypto can be absent on reduced node installations 1179 let _crypto: typeof import("crypto") | undefined; 1180 try { 1181 _crypto = require("crypto"); 1182 } 1183 catch { 1184 _crypto = undefined; 1185 } 1186 let activeSession: import("inspector").Session | "stopping" | undefined; 1187 let profilePath = "./profile.cpuprofile"; 1188 1189 const realpathSync = _fs.realpathSync.native ?? _fs.realpathSync; 1190 1191 const Buffer: { 1192 new (input: string, encoding?: string): any; 1193 from?(input: string, encoding?: string): any; 1194 } = require("buffer").Buffer; 1195 1196 const nodeVersion = getNodeMajorVersion(); 1197 const isNode4OrLater = nodeVersion! >= 4; 1198 const isLinuxOrMacOs = process.platform === "linux" || process.platform === "darwin"; 1199 1200 const platform: string = _os.platform(); 1201 const useCaseSensitiveFileNames = isFileSystemCaseSensitive(); 1202 const fsSupportsRecursiveFsWatch = isNode4OrLater && (process.platform === "win32" || process.platform === "darwin"); 1203 const getCurrentDirectory = memoize(() => process.cwd()); 1204 const { watchFile, watchDirectory } = createSystemWatchFunctions({ 1205 pollingWatchFile: createSingleFileWatcherPerName(fsWatchFileWorker, useCaseSensitiveFileNames), 1206 getModifiedTime, 1207 setTimeout, 1208 clearTimeout, 1209 fsWatch, 1210 useCaseSensitiveFileNames, 1211 getCurrentDirectory, 1212 fileExists, 1213 // Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows 1214 // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643) 1215 fsSupportsRecursiveFsWatch, 1216 directoryExists, 1217 getAccessibleSortedChildDirectories: path => getAccessibleFileSystemEntries(path).directories, 1218 realpath, 1219 tscWatchFile: process.env.TSC_WATCHFILE, 1220 useNonPollingWatchers: process.env.TSC_NONPOLLING_WATCHER, 1221 tscWatchDirectory: process.env.TSC_WATCHDIRECTORY, 1222 }); 1223 const nodeSystem: System = { 1224 args: process.argv.slice(2), 1225 newLine: _os.EOL, 1226 useCaseSensitiveFileNames, 1227 write(s: string): void { 1228 process.stdout.write(s); 1229 }, 1230 writeOutputIsTTY() { 1231 return process.stdout.isTTY; 1232 }, 1233 readFile, 1234 writeFile, 1235 watchFile, 1236 watchDirectory, 1237 resolvePath: path => _path.resolve(path), 1238 fileExists, 1239 directoryExists, 1240 createDirectory(directoryName: string) { 1241 if (!nodeSystem.directoryExists(directoryName)) { 1242 // Wrapped in a try-catch to prevent crashing if we are in a race 1243 // with another copy of ourselves to create the same directory 1244 try { 1245 _fs.mkdirSync(directoryName); 1246 } 1247 catch (e) { 1248 if (e.code !== "EEXIST") { 1249 // Failed for some other reason (access denied?); still throw 1250 throw e; 1251 } 1252 } 1253 } 1254 }, 1255 getExecutingFilePath() { 1256 return __filename; 1257 }, 1258 getCurrentDirectory, 1259 getDirectories, 1260 getEnvironmentVariable(name: string) { 1261 return process.env[name] || ""; 1262 }, 1263 readDirectory, 1264 getModifiedTime, 1265 setModifiedTime, 1266 deleteFile, 1267 createHash: _crypto ? createSHA256Hash : generateDjb2Hash, 1268 createSHA256Hash: _crypto ? createSHA256Hash : undefined, 1269 getMemoryUsage() { 1270 if (global.gc) { 1271 global.gc(); 1272 } 1273 return process.memoryUsage().heapUsed; 1274 }, 1275 getFileSize(path) { 1276 try { 1277 const stat = statSync(path); 1278 if (stat?.isFile()) { 1279 return stat.size; 1280 } 1281 } 1282 catch { /*ignore*/ } 1283 return 0; 1284 }, 1285 exit(exitCode?: number): void { 1286 disableCPUProfiler(() => process.exit(exitCode)); 1287 }, 1288 enableCPUProfiler, 1289 disableCPUProfiler, 1290 cpuProfilingEnabled: () => !!activeSession || contains(process.execArgv, "--cpu-prof") || contains(process.execArgv, "--prof"), 1291 realpath, 1292 debugMode: !!process.env.NODE_INSPECTOR_IPC || !!process.env.VSCODE_INSPECTOR_OPTIONS || some(<string[]>process.execArgv, arg => /^--(inspect|debug)(-brk)?(=\d+)?$/i.test(arg)), 1293 tryEnableSourceMapsForHost() { 1294 try { 1295 require("source-map-support").install(); 1296 } 1297 catch { 1298 // Could not enable source maps. 1299 } 1300 }, 1301 setTimeout, 1302 clearTimeout, 1303 clearScreen: () => { 1304 process.stdout.write("\x1Bc"); 1305 }, 1306 setBlocking: () => { 1307 if (process.stdout && process.stdout._handle && process.stdout._handle.setBlocking) { 1308 process.stdout._handle.setBlocking(true); 1309 } 1310 }, 1311 bufferFrom, 1312 base64decode: input => bufferFrom(input, "base64").toString("utf8"), 1313 base64encode: input => bufferFrom(input).toString("base64"), 1314 require: (baseDir, moduleName) => { 1315 try { 1316 const modulePath = resolveJSModule(moduleName, baseDir, nodeSystem); 1317 return { module: require(modulePath), modulePath, error: undefined }; 1318 } 1319 catch (error) { 1320 return { module: undefined, modulePath: undefined, error }; 1321 } 1322 } 1323 }; 1324 return nodeSystem; 1325 1326 /** 1327 * `throwIfNoEntry` was added so recently that it's not in the node types. 1328 * This helper encapsulates the mitigating usage of `any`. 1329 * See https://github.com/nodejs/node/pull/33716 1330 */ 1331 function statSync(path: string): import("fs").Stats | undefined { 1332 // throwIfNoEntry will be ignored by older versions of node 1333 return (_fs as any).statSync(path, { throwIfNoEntry: false }); 1334 } 1335 1336 /** 1337 * Uses the builtin inspector APIs to capture a CPU profile 1338 * See https://nodejs.org/api/inspector.html#inspector_example_usage for details 1339 */ 1340 function enableCPUProfiler(path: string, cb: () => void) { 1341 if (activeSession) { 1342 cb(); 1343 return false; 1344 } 1345 const inspector: typeof import("inspector") = require("inspector"); 1346 if (!inspector || !inspector.Session) { 1347 cb(); 1348 return false; 1349 } 1350 const session = new inspector.Session(); 1351 session.connect(); 1352 1353 session.post("Profiler.enable", () => { 1354 session.post("Profiler.start", () => { 1355 activeSession = session; 1356 profilePath = path; 1357 cb(); 1358 }); 1359 }); 1360 return true; 1361 } 1362 1363 /** 1364 * Strips non-TS paths from the profile, so users with private projects shouldn't 1365 * need to worry about leaking paths by submitting a cpu profile to us 1366 */ 1367 function cleanupPaths(profile: import("inspector").Profiler.Profile) { 1368 let externalFileCounter = 0; 1369 const remappedPaths = new Map<string, string>(); 1370 const normalizedDir = normalizeSlashes(__dirname); 1371 // Windows rooted dir names need an extra `/` prepended to be valid file:/// urls 1372 const fileUrlRoot = `file://${getRootLength(normalizedDir) === 1 ? "" : "/"}${normalizedDir}`; 1373 for (const node of profile.nodes) { 1374 if (node.callFrame.url) { 1375 const url = normalizeSlashes(node.callFrame.url); 1376 if (containsPath(fileUrlRoot, url, useCaseSensitiveFileNames)) { 1377 node.callFrame.url = getRelativePathToDirectoryOrUrl(fileUrlRoot, url, fileUrlRoot, createGetCanonicalFileName(useCaseSensitiveFileNames), /*isAbsolutePathAnUrl*/ true); 1378 } 1379 else if (!nativePattern.test(url)) { 1380 node.callFrame.url = (remappedPaths.has(url) ? remappedPaths : remappedPaths.set(url, `external${externalFileCounter}.js`)).get(url)!; 1381 externalFileCounter++; 1382 } 1383 } 1384 } 1385 return profile; 1386 } 1387 1388 function disableCPUProfiler(cb: () => void) { 1389 if (activeSession && activeSession !== "stopping") { 1390 const s = activeSession; 1391 activeSession.post("Profiler.stop", (err, { profile }) => { 1392 if (!err) { 1393 try { 1394 if (statSync(profilePath)?.isDirectory()) { 1395 profilePath = _path.join(profilePath, `${(new Date()).toISOString().replace(/:/g, "-")}+P${process.pid}.cpuprofile`); 1396 } 1397 } 1398 catch { 1399 // do nothing and ignore fallible fs operation 1400 } 1401 try { 1402 _fs.mkdirSync(_path.dirname(profilePath), { recursive: true }); 1403 } 1404 catch { 1405 // do nothing and ignore fallible fs operation 1406 } 1407 _fs.writeFileSync(profilePath, JSON.stringify(cleanupPaths(profile))); 1408 } 1409 activeSession = undefined; 1410 s.disconnect(); 1411 cb(); 1412 }); 1413 activeSession = "stopping"; 1414 return true; 1415 } 1416 else { 1417 cb(); 1418 return false; 1419 } 1420 } 1421 1422 function bufferFrom(input: string, encoding?: string): Buffer { 1423 // See https://github.com/Microsoft/TypeScript/issues/25652 1424 return Buffer.from && (Buffer.from as Function) !== Int8Array.from 1425 ? Buffer.from(input, encoding) 1426 : new Buffer(input, encoding); 1427 } 1428 1429 function isFileSystemCaseSensitive(): boolean { 1430 // win32\win64 are case insensitive platforms 1431 if (platform === "win32" || platform === "win64") { 1432 return false; 1433 } 1434 // If this file exists under a different case, we must be case-insensitve. 1435 return !fileExists(swapCase(__filename)); 1436 } 1437 1438 /** Convert all lowercase chars to uppercase, and vice-versa */ 1439 function swapCase(s: string): string { 1440 return s.replace(/\w/g, (ch) => { 1441 const up = ch.toUpperCase(); 1442 return ch === up ? ch.toLowerCase() : up; 1443 }); 1444 } 1445 1446 function fsWatchFileWorker(fileName: string, callback: FileWatcherCallback, pollingInterval: number): FileWatcher { 1447 _fs.watchFile(fileName, { persistent: true, interval: pollingInterval }, fileChanged); 1448 let eventKind: FileWatcherEventKind; 1449 return { 1450 close: () => _fs.unwatchFile(fileName, fileChanged) 1451 }; 1452 1453 function fileChanged(curr: any, prev: any) { 1454 // previous event kind check is to ensure we recongnize the file as previously also missing when it is restored or renamed twice (that is it disappears and reappears) 1455 // In such case, prevTime returned is same as prev time of event when file was deleted as per node documentation 1456 const isPreviouslyDeleted = +prev.mtime === 0 || eventKind === FileWatcherEventKind.Deleted; 1457 if (+curr.mtime === 0) { 1458 if (isPreviouslyDeleted) { 1459 // Already deleted file, no need to callback again 1460 return; 1461 } 1462 eventKind = FileWatcherEventKind.Deleted; 1463 } 1464 else if (isPreviouslyDeleted) { 1465 eventKind = FileWatcherEventKind.Created; 1466 } 1467 // If there is no change in modified time, ignore the event 1468 else if (+curr.mtime === +prev.mtime) { 1469 return; 1470 } 1471 else { 1472 // File changed 1473 eventKind = FileWatcherEventKind.Changed; 1474 } 1475 callback(fileName, eventKind); 1476 } 1477 } 1478 1479 function fsWatch( 1480 fileOrDirectory: string, 1481 entryKind: FileSystemEntryKind, 1482 callback: FsWatchCallback, 1483 recursive: boolean, 1484 fallbackPollingInterval: PollingInterval, 1485 fallbackOptions: WatchOptions | undefined 1486 ): FileWatcher { 1487 let options: any; 1488 let lastDirectoryPartWithDirectorySeparator: string | undefined; 1489 let lastDirectoryPart: string | undefined; 1490 if (isLinuxOrMacOs) { 1491 lastDirectoryPartWithDirectorySeparator = fileOrDirectory.substr(fileOrDirectory.lastIndexOf(directorySeparator)); 1492 lastDirectoryPart = lastDirectoryPartWithDirectorySeparator.slice(directorySeparator.length); 1493 } 1494 /** Watcher for the file system entry depending on whether it is missing or present */ 1495 let watcher = !fileSystemEntryExists(fileOrDirectory, entryKind) ? 1496 watchMissingFileSystemEntry() : 1497 watchPresentFileSystemEntry(); 1498 return { 1499 close: () => { 1500 // Close the watcher (either existing file system entry watcher or missing file system entry watcher) 1501 watcher.close(); 1502 watcher = undefined!; 1503 } 1504 }; 1505 1506 /** 1507 * Invoke the callback with rename and update the watcher if not closed 1508 * @param createWatcher 1509 */ 1510 function invokeCallbackAndUpdateWatcher(createWatcher: () => FileWatcher) { 1511 sysLog(`sysLog:: ${fileOrDirectory}:: Changing watcher to ${createWatcher === watchPresentFileSystemEntry ? "Present" : "Missing"}FileSystemEntryWatcher`); 1512 // Call the callback for current directory 1513 callback("rename", ""); 1514 1515 // If watcher is not closed, update it 1516 if (watcher) { 1517 watcher.close(); 1518 watcher = createWatcher(); 1519 } 1520 } 1521 1522 /** 1523 * Watch the file or directory that is currently present 1524 * and when the watched file or directory is deleted, switch to missing file system entry watcher 1525 */ 1526 function watchPresentFileSystemEntry(): FileWatcher { 1527 // Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows 1528 // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643) 1529 if (options === undefined) { 1530 if (fsSupportsRecursiveFsWatch) { 1531 options = { persistent: true, recursive: !!recursive }; 1532 } 1533 else { 1534 options = { persistent: true }; 1535 } 1536 } 1537 try { 1538 const presentWatcher = _fs.watch( 1539 fileOrDirectory, 1540 options, 1541 isLinuxOrMacOs ? 1542 callbackChangingToMissingFileSystemEntry : 1543 callback 1544 ); 1545 // Watch the missing file or directory or error 1546 presentWatcher.on("error", () => invokeCallbackAndUpdateWatcher(watchMissingFileSystemEntry)); 1547 return presentWatcher; 1548 } 1549 catch (e) { 1550 // Catch the exception and use polling instead 1551 // Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point 1552 // so instead of throwing error, use fs.watchFile 1553 return watchPresentFileSystemEntryWithFsWatchFile(); 1554 } 1555 } 1556 1557 function callbackChangingToMissingFileSystemEntry(event: "rename" | "change", relativeName: string | undefined) { 1558 // because relativeName is not guaranteed to be correct we need to check on each rename with few combinations 1559 // Eg on ubuntu while watching app/node_modules the relativeName is "node_modules" which is neither relative nor full path 1560 return event === "rename" && 1561 (!relativeName || 1562 relativeName === lastDirectoryPart || 1563 (relativeName.lastIndexOf(lastDirectoryPartWithDirectorySeparator!) !== -1 && relativeName.lastIndexOf(lastDirectoryPartWithDirectorySeparator!) === relativeName.length - lastDirectoryPartWithDirectorySeparator!.length)) && 1564 !fileSystemEntryExists(fileOrDirectory, entryKind) ? 1565 invokeCallbackAndUpdateWatcher(watchMissingFileSystemEntry) : 1566 callback(event, relativeName); 1567 } 1568 1569 /** 1570 * Watch the file or directory using fs.watchFile since fs.watch threw exception 1571 * Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point 1572 */ 1573 function watchPresentFileSystemEntryWithFsWatchFile(): FileWatcher { 1574 sysLog(`sysLog:: ${fileOrDirectory}:: Changing to fsWatchFile`); 1575 return watchFile( 1576 fileOrDirectory, 1577 createFileWatcherCallback(callback), 1578 fallbackPollingInterval, 1579 fallbackOptions 1580 ); 1581 } 1582 1583 /** 1584 * Watch the file or directory that is missing 1585 * and switch to existing file or directory when the missing filesystem entry is created 1586 */ 1587 function watchMissingFileSystemEntry(): FileWatcher { 1588 return watchFile( 1589 fileOrDirectory, 1590 (_fileName, eventKind) => { 1591 if (eventKind === FileWatcherEventKind.Created && fileSystemEntryExists(fileOrDirectory, entryKind)) { 1592 // Call the callback for current file or directory 1593 // For now it could be callback for the inner directory creation, 1594 // but just return current directory, better than current no-op 1595 invokeCallbackAndUpdateWatcher(watchPresentFileSystemEntry); 1596 } 1597 }, 1598 fallbackPollingInterval, 1599 fallbackOptions 1600 ); 1601 } 1602 } 1603 1604 function readFileWorker(fileName: string, _encoding?: string): string | undefined { 1605 let buffer: Buffer; 1606 try { 1607 buffer = _fs.readFileSync(fileName); 1608 } 1609 catch (e) { 1610 return undefined; 1611 } 1612 let len = buffer.length; 1613 if (len >= 2 && buffer[0] === 0xFE && buffer[1] === 0xFF) { 1614 // Big endian UTF-16 byte order mark detected. Since big endian is not supported by node.js, 1615 // flip all byte pairs and treat as little endian. 1616 len &= ~1; // Round down to a multiple of 2 1617 for (let i = 0; i < len; i += 2) { 1618 const temp = buffer[i]; 1619 buffer[i] = buffer[i + 1]; 1620 buffer[i + 1] = temp; 1621 } 1622 return buffer.toString("utf16le", 2); 1623 } 1624 if (len >= 2 && buffer[0] === 0xFF && buffer[1] === 0xFE) { 1625 // Little endian UTF-16 byte order mark detected 1626 return buffer.toString("utf16le", 2); 1627 } 1628 if (len >= 3 && buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) { 1629 // UTF-8 byte order mark detected 1630 return buffer.toString("utf8", 3); 1631 } 1632 // Default is UTF-8 with no byte order mark 1633 return buffer.toString("utf8"); 1634 } 1635 1636 function readFile(fileName: string, _encoding?: string): string | undefined { 1637 perfLogger.logStartReadFile(fileName); 1638 const file = readFileWorker(fileName, _encoding); 1639 perfLogger.logStopReadFile(); 1640 return file; 1641 } 1642 1643 function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void { 1644 perfLogger.logEvent("WriteFile: " + fileName); 1645 // If a BOM is required, emit one 1646 if (writeByteOrderMark) { 1647 data = byteOrderMarkIndicator + data; 1648 } 1649 1650 let fd: number | undefined; 1651 1652 try { 1653 fd = _fs.openSync(fileName, "w"); 1654 _fs.writeSync(fd, data, /*position*/ undefined, "utf8"); 1655 } 1656 finally { 1657 if (fd !== undefined) { 1658 _fs.closeSync(fd); 1659 } 1660 } 1661 } 1662 1663 function getAccessibleFileSystemEntries(path: string): FileSystemEntries { 1664 perfLogger.logEvent("ReadDir: " + (path || ".")); 1665 try { 1666 const entries = _fs.readdirSync(path || ".", { withFileTypes: true }); 1667 const files: string[] = []; 1668 const directories: string[] = []; 1669 for (const dirent of entries) { 1670 // withFileTypes is not supported before Node 10.10. 1671 const entry = typeof dirent === "string" ? dirent : dirent.name; 1672 1673 // This is necessary because on some file system node fails to exclude 1674 // "." and "..". See https://github.com/nodejs/node/issues/4002 1675 if (entry === "." || entry === "..") { 1676 continue; 1677 } 1678 1679 let stat: any; 1680 if (typeof dirent === "string" || dirent.isSymbolicLink()) { 1681 const name = combinePaths(path, entry); 1682 1683 try { 1684 stat = statSync(name); 1685 if (!stat) { 1686 continue; 1687 } 1688 } 1689 catch (e) { 1690 continue; 1691 } 1692 } 1693 else { 1694 stat = dirent; 1695 } 1696 1697 if (stat.isFile()) { 1698 files.push(entry); 1699 } 1700 else if (stat.isDirectory()) { 1701 directories.push(entry); 1702 } 1703 } 1704 files.sort(); 1705 directories.sort(); 1706 return { files, directories }; 1707 } 1708 catch (e) { 1709 return emptyFileSystemEntries; 1710 } 1711 } 1712 1713 function readDirectory(path: string, extensions?: readonly string[], excludes?: readonly string[], includes?: readonly string[], depth?: number): string[] { 1714 return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames, process.cwd(), depth, getAccessibleFileSystemEntries, realpath); 1715 } 1716 1717 function fileSystemEntryExists(path: string, entryKind: FileSystemEntryKind): boolean { 1718 // Since the error thrown by fs.statSync isn't used, we can avoid collecting a stack trace to improve 1719 // the CPU time performance. 1720 const originalStackTraceLimit = Error.stackTraceLimit; 1721 Error.stackTraceLimit = 0; 1722 1723 try { 1724 const stat = statSync(path); 1725 if (!stat) { 1726 return false; 1727 } 1728 switch (entryKind) { 1729 case FileSystemEntryKind.File: return stat.isFile(); 1730 case FileSystemEntryKind.Directory: return stat.isDirectory(); 1731 default: return false; 1732 } 1733 } 1734 catch (e) { 1735 return false; 1736 } 1737 finally { 1738 Error.stackTraceLimit = originalStackTraceLimit; 1739 } 1740 } 1741 1742 function fileExists(path: string): boolean { 1743 return fileSystemEntryExists(path, FileSystemEntryKind.File); 1744 } 1745 1746 function directoryExists(path: string): boolean { 1747 return fileSystemEntryExists(path, FileSystemEntryKind.Directory); 1748 } 1749 1750 function getDirectories(path: string): string[] { 1751 return getAccessibleFileSystemEntries(path).directories.slice(); 1752 } 1753 1754 function realpath(path: string): string { 1755 try { 1756 return realpathSync(path); 1757 } 1758 catch { 1759 return path; 1760 } 1761 } 1762 1763 function getModifiedTime(path: string) { 1764 try { 1765 return statSync(path)?.mtime; 1766 } 1767 catch (e) { 1768 return undefined; 1769 } 1770 } 1771 1772 function setModifiedTime(path: string, time: Date) { 1773 try { 1774 _fs.utimesSync(path, time, time); 1775 } 1776 catch (e) { 1777 return; 1778 } 1779 } 1780 1781 function deleteFile(path: string) { 1782 try { 1783 return _fs.unlinkSync(path); 1784 } 1785 catch (e) { 1786 return; 1787 } 1788 } 1789 1790 function createSHA256Hash(data: string): string { 1791 const hash = _crypto!.createHash("sha256"); 1792 hash.update(data); 1793 return hash.digest("hex"); 1794 } 1795 } 1796 1797 let sys: System | undefined; 1798 if (typeof process !== "undefined" && process.nextTick && !process.browser && typeof require !== "undefined") { 1799 // process and process.nextTick checks if current environment is node-like 1800 // process.browser check excludes webpack and browserify 1801 sys = getNodeSystem(); 1802 } 1803 if (sys) { 1804 // patch writefile to create folder before writing the file 1805 patchWriteFileEnsuringDirectory(sys); 1806 } 1807 return sys!; 1808 })(); 1809 1810 if (sys && sys.getEnvironmentVariable) { 1811 setCustomPollingValues(sys); 1812 Debug.setAssertionLevel(/^development$/i.test(sys.getEnvironmentVariable("NODE_ENV")) 1813 ? AssertionLevel.Normal 1814 : AssertionLevel.None); 1815 } 1816 if (sys && sys.debugMode) { 1817 Debug.isDebugging = true; 1818 } 1819} 1820