Skip to content

Commit 2684784

Browse files
committed
Handle source file versioning better since the emit depends on it
This ensures that when file is deleted and re-created, the file version isnt same to ensure emits correctly Fixes microsoft#21444
1 parent 384aad6 commit 2684784

2 files changed

Lines changed: 58 additions & 57 deletions

File tree

src/compiler/watch.ts

Lines changed: 56 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,9 @@ namespace ts {
420420
}
421421
}
422422

423+
const intialVersion = 1;
424+
const intialVersionString = "1";
425+
423426
/**
424427
* Creates the watch from the host for root files and compiler options
425428
*/
@@ -429,19 +432,25 @@ namespace ts {
429432
*/
430433
export function createWatchProgram<T extends BuilderProgram>(host: WatchCompilerHostOfConfigFile<T>): WatchOfConfigFile<T>;
431434
export function createWatchProgram<T extends BuilderProgram>(host: WatchCompilerHostOfFilesAndCompilerOptions<T> & WatchCompilerHostOfConfigFile<T>): WatchOfFilesAndCompilerOptions<T> | WatchOfConfigFile<T> {
432-
interface HostFileInfo {
435+
interface FilePresentOnHost {
433436
version: number;
434437
sourceFile: SourceFile;
435438
fileWatcher: FileWatcher;
436439
}
440+
type FileMissingOnHost = string;
441+
interface FilePresenceUnknownOnHost {
442+
version: number;
443+
}
444+
type FileMayBePresentOnHost = FilePresentOnHost | FilePresenceUnknownOnHost;
445+
type HostFileInfo = FilePresentOnHost | FileMissingOnHost | FilePresenceUnknownOnHost;
437446

438447
let builderProgram: T;
439448
let reloadLevel: ConfigFileProgramReloadLevel; // level to indicate if the program needs to be reloaded from config file/just filenames etc
440449
let missingFilesMap: Map<FileWatcher>; // Map of file watchers for the missing files
441450
let watchedWildcardDirectories: Map<WildcardDirectoryWatcher>; // map of watchers for the wild card directories in the config file
442451
let timerToUpdateProgram: any; // timer callback to recompile the program
443452

444-
const sourceFilesCache = createMap<HostFileInfo | string>(); // Cache that stores the source file and version info
453+
const sourceFilesCache = createMap<HostFileInfo>(); // Cache that stores the source file and version info
445454
let missingFilePathsRequestedForRelease: Path[]; // These paths are held temparirly so that we can remove the entry from source file cache if the file is not tracked by missing files
446455
let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations
447456
let hasChangedAutomaticTypeDirectiveNames = false; // True if the automatic type directives have changed
@@ -627,11 +636,20 @@ namespace ts {
627636
return ts.toPath(fileName, currentDirectory, getCanonicalFileName);
628637
}
629638

639+
function isFileMissingOnHost(hostSourceFile: HostFileInfo): hostSourceFile is FileMissingOnHost {
640+
return isString(hostSourceFile);
641+
}
642+
643+
function isFilePresentOnHost(hostSourceFile: FileMayBePresentOnHost): hostSourceFile is FilePresentOnHost {
644+
return !!(hostSourceFile as FilePresentOnHost).sourceFile;
645+
}
646+
630647
function fileExists(fileName: string) {
631648
const path = toPath(fileName);
632-
const hostSourceFileInfo = sourceFilesCache.get(path);
633-
if (hostSourceFileInfo !== undefined) {
634-
return !isString(hostSourceFileInfo);
649+
// If file is missing on host from cache, we can definitely say file doesnt exist
650+
// otherwise we need to ensure from the disk
651+
if (isFileMissingOnHost(sourceFilesCache.get(path))) {
652+
return true;
635653
}
636654

637655
return directoryStructureHost.fileExists(fileName);
@@ -640,39 +658,42 @@ namespace ts {
640658
function getVersionedSourceFileByPath(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile {
641659
const hostSourceFile = sourceFilesCache.get(path);
642660
// No source file on the host
643-
if (isString(hostSourceFile)) {
661+
if (isFileMissingOnHost(hostSourceFile)) {
644662
return undefined;
645663
}
646664

647665
// Create new source file if requested or the versions dont match
648-
if (!hostSourceFile || shouldCreateNewSourceFile || hostSourceFile.version.toString() !== hostSourceFile.sourceFile.version) {
666+
if (!hostSourceFile || shouldCreateNewSourceFile || !isFilePresentOnHost(hostSourceFile) || hostSourceFile.version.toString() !== hostSourceFile.sourceFile.version) {
649667
const sourceFile = getNewSourceFile();
650668
if (hostSourceFile) {
651669
if (shouldCreateNewSourceFile) {
652670
hostSourceFile.version++;
653671
}
672+
654673
if (sourceFile) {
655-
hostSourceFile.sourceFile = sourceFile;
674+
// Set the source file and create file watcher now that file was present on the disk
675+
(hostSourceFile as FilePresentOnHost).sourceFile = sourceFile;
656676
sourceFile.version = hostSourceFile.version.toString();
657-
if (!hostSourceFile.fileWatcher) {
658-
hostSourceFile.fileWatcher = watchFilePath(host, fileName, onSourceFileChange, path, writeLog);
677+
if (!(hostSourceFile as FilePresentOnHost).fileWatcher) {
678+
(hostSourceFile as FilePresentOnHost).fileWatcher = watchFilePath(host, fileName, onSourceFileChange, path, writeLog);
659679
}
660680
}
661681
else {
662682
// There is no source file on host any more, close the watch, missing file paths will track it
663-
hostSourceFile.fileWatcher.close();
683+
if (isFilePresentOnHost(hostSourceFile)) {
684+
hostSourceFile.fileWatcher.close();
685+
}
664686
sourceFilesCache.set(path, hostSourceFile.version.toString());
665687
}
666688
}
667689
else {
668-
let fileWatcher: FileWatcher;
669690
if (sourceFile) {
670-
sourceFile.version = "1";
671-
fileWatcher = watchFilePath(host, fileName, onSourceFileChange, path, writeLog);
672-
sourceFilesCache.set(path, { sourceFile, version: 1, fileWatcher });
691+
sourceFile.version = intialVersionString;
692+
const fileWatcher = watchFilePath(host, fileName, onSourceFileChange, path, writeLog);
693+
sourceFilesCache.set(path, { sourceFile, version: intialVersion, fileWatcher });
673694
}
674695
else {
675-
sourceFilesCache.set(path, "0");
696+
sourceFilesCache.set(path, intialVersionString);
676697
}
677698
}
678699
return sourceFile;
@@ -697,20 +718,22 @@ namespace ts {
697718
}
698719
}
699720

700-
function removeSourceFile(path: Path) {
721+
function nextSourceFileVersion(path: Path) {
701722
const hostSourceFile = sourceFilesCache.get(path);
702723
if (hostSourceFile !== undefined) {
703-
if (!isString(hostSourceFile)) {
704-
hostSourceFile.fileWatcher.close();
705-
resolutionCache.invalidateResolutionOfFile(path);
724+
if (isFileMissingOnHost(hostSourceFile)) {
725+
// The next version, lets set it as presence unknown file
726+
sourceFilesCache.set(path, { version: Number(hostSourceFile) + 1 });
727+
}
728+
else {
729+
hostSourceFile.version++;
706730
}
707-
sourceFilesCache.delete(path);
708731
}
709732
}
710733

711734
function getSourceVersion(path: Path): string {
712735
const hostSourceFile = sourceFilesCache.get(path);
713-
return !hostSourceFile || isString(hostSourceFile) ? undefined : hostSourceFile.version.toString();
736+
return !hostSourceFile || isFileMissingOnHost(hostSourceFile) ? undefined : hostSourceFile.version.toString();
714737
}
715738

716739
function onReleaseOldSourceFile(oldSourceFile: SourceFile, _oldOptions: CompilerOptions) {
@@ -721,10 +744,10 @@ namespace ts {
721744
// there was version update and new source file was created.
722745
if (hostSourceFileInfo) {
723746
// record the missing file paths so they can be removed later if watchers arent tracking them
724-
if (isString(hostSourceFileInfo)) {
747+
if (isFileMissingOnHost(hostSourceFileInfo)) {
725748
(missingFilePathsRequestedForRelease || (missingFilePathsRequestedForRelease = [])).push(oldSourceFile.path);
726749
}
727-
else if (hostSourceFileInfo.sourceFile === oldSourceFile) {
750+
else if ((hostSourceFileInfo as FilePresentOnHost).sourceFile === oldSourceFile) {
728751
sourceFilesCache.delete(oldSourceFile.path);
729752
resolutionCache.removeResolutionsOfFile(oldSourceFile.path);
730753
}
@@ -808,27 +831,12 @@ namespace ts {
808831

809832
function onSourceFileChange(fileName: string, eventKind: FileWatcherEventKind, path: Path) {
810833
updateCachedSystemWithFile(fileName, path, eventKind);
811-
const hostSourceFile = sourceFilesCache.get(path);
812-
if (hostSourceFile) {
813-
// Update the cache
814-
if (eventKind === FileWatcherEventKind.Deleted) {
815-
resolutionCache.invalidateResolutionOfFile(path);
816-
if (!isString(hostSourceFile)) {
817-
hostSourceFile.fileWatcher.close();
818-
sourceFilesCache.set(path, (++hostSourceFile.version).toString());
819-
}
820-
}
821-
else {
822-
// Deleted file created
823-
if (isString(hostSourceFile)) {
824-
sourceFilesCache.delete(path);
825-
}
826-
else {
827-
// file changed - just update the version
828-
hostSourceFile.version++;
829-
}
830-
}
834+
835+
// Update the source file cache
836+
if (eventKind === FileWatcherEventKind.Deleted && sourceFilesCache.get(path)) {
837+
resolutionCache.invalidateResolutionOfFile(path);
831838
}
839+
nextSourceFileVersion(path);
832840

833841
// Update the program
834842
scheduleProgramUpdate();
@@ -856,7 +864,7 @@ namespace ts {
856864
missingFilesMap.delete(missingFilePath);
857865

858866
// Delete the entry in the source files cache so that new source file is created
859-
removeSourceFile(missingFilePath);
867+
nextSourceFileVersion(missingFilePath);
860868

861869
// When a missing file is created, we should update the graph.
862870
scheduleProgramUpdate();
@@ -885,17 +893,10 @@ namespace ts {
885893
const fileOrDirectoryPath = toPath(fileOrDirectory);
886894

887895
// Since the file existance changed, update the sourceFiles cache
888-
const result = cachedDirectoryStructureHost && cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
889-
890-
// Instead of deleting the file, mark it as changed instead
891-
// Many times node calls add/remove/file when watching directories recursively
892-
const hostSourceFile = sourceFilesCache.get(fileOrDirectoryPath);
893-
if (hostSourceFile && !isString(hostSourceFile) && (result ? result.fileExists : directoryStructureHost.fileExists(fileOrDirectory))) {
894-
hostSourceFile.version++;
895-
}
896-
else {
897-
removeSourceFile(fileOrDirectoryPath);
896+
if (cachedDirectoryStructureHost) {
897+
cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
898898
}
899+
nextSourceFileVersion(fileOrDirectoryPath);
899900

900901
// If the the added or created file or directory is not supported file name, ignore the file
901902
// But when watched directory is added/removed, we need to reload the file list

src/harness/unittests/tscWatchMode.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1730,7 +1730,7 @@ namespace ts.tscWatch {
17301730
const configFile: FileOrFolder = {
17311731
path: `${projectLocation}/tsconfig.json`,
17321732
content: JSON.stringify({
1733-
"include": [
1733+
include: [
17341734
"app/**/*.ts"
17351735
]
17361736
})
@@ -1740,7 +1740,7 @@ namespace ts.tscWatch {
17401740
createWatchOfConfigFile("tsconfig.json", host);
17411741
verifyProgram();
17421742

1743-
file.content += "var b = 10;";
1743+
file.content += "\nvar b = 10;";
17441744

17451745
host.reloadFS(files, { invokeFileDeleteCreateAsPartInsteadOfChange: true });
17461746
host.runQueuedTimeoutCallbacks();

0 commit comments

Comments
 (0)