Update watches to wild card directories, input files, config files when project invalidates

This commit is contained in:
Sheetal Nandi 2018-09-10 16:17:52 -07:00
parent 228858f36c
commit 6c57ebd00b
3 changed files with 155 additions and 79 deletions

View File

@ -399,7 +399,9 @@ namespace ts {
let reportFileChangeDetected = false;
// Watches for the solution
const existingWatchersForWildcards = createFileMap<Map<WildcardDirectoryWatcher>>(toPath);
const allWatchedWildcardDirectories = createFileMap<Map<WildcardDirectoryWatcher>>(toPath);
const allWatchedInputFiles = createFileMap<Map<FileWatcher>>(toPath);
const allWatchedConfigFiles = createFileMap<FileWatcher>(toPath);
return {
buildAllProjects,
@ -439,7 +441,9 @@ namespace ts {
timerToBuildInvalidatedProject = undefined;
}
reportFileChangeDetected = false;
existingWatchersForWildcards.forEach(wildCardWatches => clearMap(wildCardWatches, closeFileWatcherOf));
clearMap(allWatchedWildcardDirectories, wildCardWatches => clearMap(wildCardWatches, closeFileWatcherOf));
clearMap(allWatchedInputFiles, inputFileWatches => clearMap(inputFileWatches, closeFileWatcher));
clearMap(allWatchedConfigFiles, closeFileWatcher);
}
function isParsedCommandLine(entry: ConfigFileCacheEntry): entry is ParsedCommandLine {
@ -493,48 +497,73 @@ namespace ts {
const cfg = parseConfigFile(resolved);
if (cfg) {
// Watch this file
hostWithWatch.watchFile(resolved, () => {
configFileCache.removeKey(resolved);
invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.Full);
});
watchConfigFile(resolved);
// Update watchers for wildcard directories
if (cfg.configFileSpecs) {
const existingWatches = existingWatchersForWildcards.getValue(resolved);
let newWatches: Map<WildcardDirectoryWatcher> | undefined;
if (!existingWatches) {
newWatches = createMap();
existingWatchersForWildcards.setValue(resolved, newWatches);
}
updateWatchingWildcardDirectories(existingWatches || newWatches!, createMapFromTemplate(cfg.configFileSpecs.wildcardDirectories), (dir, flags) => {
return hostWithWatch.watchDirectory(dir, fileOrDirectory => {
const fileOrDirectoryPath = toPath(fileOrDirectory);
if (fileOrDirectoryPath !== toPath(dir) && hasExtension(fileOrDirectoryPath) && !isSupportedSourceFileName(fileOrDirectory, cfg.options)) {
// writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrDirectory}`);
return;
}
if (isOutputFile(fileOrDirectory, cfg)) {
// writeLog(`${fileOrDirectory} is output file`);
return;
}
invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.Partial);
}, !!(flags & WatchDirectoryFlags.Recursive));
});
}
watchWildCardDirectories(resolved, cfg);
// Watch input files
for (const input of cfg.fileNames) {
hostWithWatch.watchFile(input, () => {
invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.None);
});
}
watchInputFiles(resolved, cfg);
}
}
}
function watchConfigFile(resolved: ResolvedConfigFileName) {
if (!allWatchedConfigFiles.hasKey(resolved)) {
allWatchedConfigFiles.setValue(resolved, hostWithWatch.watchFile(resolved, () => {
configFileCache.removeKey(resolved);
invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.Full);
}));
}
}
function getOrCreateExistingWatches<T>(resolved: ResolvedConfigFileName, allWatches: ConfigFileMap<Map<T>>) {
const existingWatches = allWatches.getValue(resolved);
let newWatches: Map<T> | undefined;
if (!existingWatches) {
newWatches = createMap();
allWatches.setValue(resolved, newWatches);
}
return existingWatches || newWatches!;
}
function watchWildCardDirectories(resolved: ResolvedConfigFileName, parsed: ParsedCommandLine) {
updateWatchingWildcardDirectories(
getOrCreateExistingWatches(resolved, allWatchedWildcardDirectories),
createMapFromTemplate(parsed.configFileSpecs!.wildcardDirectories),
(dir, flags) => {
return hostWithWatch.watchDirectory(dir, fileOrDirectory => {
const fileOrDirectoryPath = toPath(fileOrDirectory);
if (fileOrDirectoryPath !== toPath(dir) && hasExtension(fileOrDirectoryPath) && !isSupportedSourceFileName(fileOrDirectory, parsed.options)) {
// writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrDirectory}`);
return;
}
if (isOutputFile(fileOrDirectory, parsed)) {
// writeLog(`${fileOrDirectory} is output file`);
return;
}
invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.Partial);
}, !!(flags & WatchDirectoryFlags.Recursive));
}
);
}
function watchInputFiles(resolved: ResolvedConfigFileName, parsed: ParsedCommandLine) {
mutateMap(
getOrCreateExistingWatches(resolved, allWatchedInputFiles),
arrayToMap(parsed.fileNames, toPath),
{
createNewValue: (_key, input) => hostWithWatch.watchFile(input, () => {
invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.None);
}),
onDeleteValue: closeFileWatcher,
}
);
}
function isOutputFile(fileName: string, configFile: ParsedCommandLine) {
if (configFile.options.noEmit) return false;
@ -879,10 +908,12 @@ namespace ts {
if (!resolved) return; // ??
const proj = parseConfigFile(resolved);
if (!proj) return; // ?
// TODO:: If full reload , update watch for wild cards
// TODO:: If full or partial reload, update watch for input files
if (reloadLevel === ConfigFileProgramReloadLevel.Partial) {
if (reloadLevel === ConfigFileProgramReloadLevel.Full) {
watchConfigFile(resolved);
watchWildCardDirectories(resolved, proj);
watchInputFiles(resolved, proj);
}
else if (reloadLevel === ConfigFileProgramReloadLevel.Partial) {
// Update file names
const result = getFileNamesFromConfigSpecs(proj.configFileSpecs!, getDirectoryPath(project), proj.options, parseConfigFileHost);
if (result.fileNames.length !== 0) {
@ -892,6 +923,7 @@ namespace ts {
proj.errors.push(getErrorForNoInputFiles(proj.configFileSpecs!, resolved));
}
proj.fileNames = result.fileNames;
watchInputFiles(resolved, proj);
}
const status = getUpToDateStatus(proj);

View File

@ -4329,7 +4329,7 @@ namespace ts {
/**
* clears already present map by calling onDeleteExistingValue callback before deleting that key/value
*/
export function clearMap<T>(map: Map<T>, onDeleteValue: (valueInMap: T, key: string) => void) {
export function clearMap<T>(map: { forEach: Map<T>["forEach"]; clear: Map<T>["clear"]; }, onDeleteValue: (valueInMap: T, key: string) => void) {
// Remove all
map.forEach(onDeleteValue);
map.clear();

View File

@ -62,13 +62,17 @@ namespace ts.tscWatch {
return getOutputFileNames(subProject, baseFileNameWithoutExtension).map(f => [f, host.getModifiedTime(f)] as OutputFileStamp);
}
function getOutputFileStamps(host: WatchedSystem): OutputFileStamp[] {
return [
function getOutputFileStamps(host: WatchedSystem, additionalFiles?: ReadonlyArray<[SubProject, string]>): OutputFileStamp[] {
const result = [
...getOutputStamps(host, SubProject.core, "anotherModule"),
...getOutputStamps(host, SubProject.core, "index"),
...getOutputStamps(host, SubProject.logic, "index"),
...getOutputStamps(host, SubProject.tests, "index"),
];
if (additionalFiles) {
additionalFiles.forEach(([subProject, baseFileNameWithoutExtension]) => result.push(...getOutputStamps(host, subProject, baseFileNameWithoutExtension)));
}
return result;
}
function verifyChangedFiles(actualStamps: OutputFileStamp[], oldTimeStamps: OutputFileStamp[], changedFiles: string[]) {
@ -108,49 +112,89 @@ namespace ts.tscWatch {
createSolutionInWatchMode();
});
it("change builds changes and reports found errors message", () => {
const host = createSolutionInWatchMode();
verifyChange(`${core[1].content}
describe("validates the changes and watched files", () => {
const newFileWithoutExtension = "newFile";
const newFile: File = {
path: projectFilePath(SubProject.core, `${newFileWithoutExtension}.ts`),
content: `export const newFileConst = 30;`
};
function createSolutionInWatchModeToVerifyChanges(additionalFiles?: ReadonlyArray<[SubProject, string]>) {
const host = createSolutionInWatchMode();
return { host, verifyChangeWithFile, verifyChangeAfterTimeout, verifyWatches };
function verifyChangeWithFile(fileName: string, content: string) {
const outputFileStamps = getOutputFileStamps(host, additionalFiles);
host.writeFile(fileName, content);
verifyChangeAfterTimeout(outputFileStamps);
}
function verifyChangeAfterTimeout(outputFileStamps: OutputFileStamp[]) {
host.checkTimeoutQueueLengthAndRun(1); // Builds core
const changedCore = getOutputFileStamps(host, additionalFiles);
verifyChangedFiles(changedCore, outputFileStamps, [
...getOutputFileNames(SubProject.core, "anotherModule"), // This should not be written really
...getOutputFileNames(SubProject.core, "index"),
...(additionalFiles ? getOutputFileNames(SubProject.core, newFileWithoutExtension) : emptyArray)
]);
host.checkTimeoutQueueLengthAndRun(1); // Builds tests
const changedTests = getOutputFileStamps(host, additionalFiles);
verifyChangedFiles(changedTests, changedCore, [
...getOutputFileNames(SubProject.tests, "index") // Again these need not be written
]);
host.checkTimeoutQueueLengthAndRun(1); // Builds logic
const changedLogic = getOutputFileStamps(host, additionalFiles);
verifyChangedFiles(changedLogic, changedTests, [
...getOutputFileNames(SubProject.logic, "index") // Again these need not be written
]);
host.checkTimeoutQueueLength(0);
checkOutputErrorsIncremental(host, emptyArray);
verifyWatches();
}
function verifyWatches() {
checkWatchedFiles(host, additionalFiles ? testProjectExpectedWatchedFiles.concat(newFile.path) : testProjectExpectedWatchedFiles);
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
checkWatchedDirectories(host, [projectPath(SubProject.core), projectPath(SubProject.logic)], /*recursive*/ true);
}
}
it("change builds changes and reports found errors message", () => {
const { host, verifyChangeWithFile, verifyChangeAfterTimeout } = createSolutionInWatchModeToVerifyChanges();
verifyChange(`${core[1].content}
export class someClass { }`);
// Another change requeues and builds it
verifyChange(core[1].content);
// Another change requeues and builds it
verifyChange(core[1].content);
// Two changes together report only single time message: File change detected. Starting incremental compilation...
const outputFileStamps = getOutputFileStamps(host);
const change1 = `${core[1].content}
export class someClass { }`;
host.writeFile(core[1].path, change1);
host.writeFile(core[1].path, `${change1}
export class someClass2 { }`);
verifyChangeAfterTimeout(outputFileStamps);
function verifyChange(coreContent: string) {
// Two changes together report only single time message: File change detected. Starting incremental compilation...
const outputFileStamps = getOutputFileStamps(host);
host.writeFile(core[1].path, coreContent);
const change1 = `${core[1].content}
export class someClass { }`;
host.writeFile(core[1].path, change1);
host.writeFile(core[1].path, `${change1}
export class someClass2 { }`);
verifyChangeAfterTimeout(outputFileStamps);
}
function verifyChangeAfterTimeout(outputFileStamps: OutputFileStamp[]) {
host.checkTimeoutQueueLengthAndRun(1); // Builds core
const changedCore = getOutputFileStamps(host);
verifyChangedFiles(changedCore, outputFileStamps, [
...getOutputFileNames(SubProject.core, "anotherModule"), // This should not be written really
...getOutputFileNames(SubProject.core, "index")
]);
host.checkTimeoutQueueLengthAndRun(1); // Builds tests
const changedTests = getOutputFileStamps(host);
verifyChangedFiles(changedTests, changedCore, [
...getOutputFileNames(SubProject.tests, "index") // Again these need not be written
]);
host.checkTimeoutQueueLengthAndRun(1); // Builds logic
const changedLogic = getOutputFileStamps(host);
verifyChangedFiles(changedLogic, changedTests, [
...getOutputFileNames(SubProject.logic, "index") // Again these need not be written
]);
host.checkTimeoutQueueLength(0);
checkOutputErrorsIncremental(host, emptyArray);
}
function verifyChange(coreContent: string) {
verifyChangeWithFile(core[1].path, coreContent);
}
});
it("builds when new file is added, and its subsequent updates", () => {
const additinalFiles: ReadonlyArray<[SubProject, string]> = [[SubProject.core, newFileWithoutExtension]];
const { verifyChangeWithFile } = createSolutionInWatchModeToVerifyChanges(additinalFiles);
verifyChange(newFile.content);
// Another change requeues and builds it
verifyChange(`${newFile.content}
export class someClass2 { }`);
function verifyChange(newFileContent: string) {
verifyChangeWithFile(newFile.path, newFileContent);
}
});
});
// TODO: write tests reporting errors but that will have more involved work since file