mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-14 19:16:17 -06:00
Update watches to wild card directories, input files, config files when project invalidates
This commit is contained in:
parent
228858f36c
commit
6c57ebd00b
@ -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);
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user