Merge pull request #16684 from amcasey/Vsts434619

Watch for the creation of missing files
This commit is contained in:
Andrew Casey
2017-06-29 13:41:18 -07:00
committed by GitHub
18 changed files with 287 additions and 47 deletions
+1
View File
@@ -138,6 +138,7 @@ var harnessSources = harnessCoreSources.concat([
"telemetry.ts",
"transform.ts",
"customTransforms.ts",
"programMissingFiles.ts",
].map(function (f) {
return path.join(unittestsDirectory, f);
})).concat([
+9
View File
@@ -1098,6 +1098,15 @@ namespace ts {
return result;
}
/**
* Creates a set from the elements of an array.
*
* @param array the array of input elements.
*/
export function arrayToSet<T>(array: T[], makeKey: (value: T) => string): Map<true> {
return arrayToMap<T, true>(array, makeKey, () => true);
}
export function cloneMap<T>(map: Map<T>) {
const clone = createMap<T>();
copyEntries(map, clone);
+19 -1
View File
@@ -472,7 +472,7 @@ namespace ts {
resolveTypeReferenceDirectiveNamesWorker = (typeReferenceDirectiveNames, containingFile) => loadWithLocalCache(typeReferenceDirectiveNames, containingFile, loader);
}
const filesByName = createMap<SourceFile>();
const filesByName = createMap<SourceFile | undefined>();
// stores 'filename -> file association' ignoring case
// used to track cases when two file names differ only in casing
const filesByNameIgnoreCase = host.useCaseSensitiveFileNames() ? createFileMap<SourceFile>(fileName => fileName.toLowerCase()) : undefined;
@@ -513,6 +513,8 @@ namespace ts {
}
}
const missingFilePaths = arrayFrom(filesByName.keys(), p => <Path>p).filter(p => !filesByName.get(p));
// unconditionally set moduleResolutionCache to undefined to avoid unnecessary leaks
moduleResolutionCache = undefined;
@@ -524,6 +526,7 @@ namespace ts {
getSourceFile,
getSourceFileByPath,
getSourceFiles: () => files,
getMissingFilePaths: () => missingFilePaths,
getCompilerOptions: () => options,
getSyntacticDiagnostics,
getOptionsDiagnostics,
@@ -862,6 +865,21 @@ namespace ts {
return oldProgram.structureIsReused;
}
// If a file has ceased to be missing, then we need to discard some of the old
// structure in order to pick it up.
// Caution: if the file has created and then deleted between since it was discovered to
// be missing, then the corresponding file watcher will have been closed and no new one
// will be created until we encounter a change that prevents complete structure reuse.
// During this interval, creation of the file will go unnoticed. We expect this to be
// both rare and low-impact.
if (oldProgram.getMissingFilePaths().some(missingFilePath => host.fileExists(missingFilePath))) {
return oldProgram.structureIsReused = StructureIsReused.SafeModules;
}
for (const p of oldProgram.getMissingFilePaths()) {
filesByName.set(p, undefined);
}
// update fileName -> file mapping
for (let i = 0; i < newSourceFiles.length; i++) {
filesByName.set(filePaths[i], newSourceFiles[i]);
+21 -4
View File
@@ -4,7 +4,13 @@ declare function setTimeout(handler: (...args: any[]) => void, timeout: number):
declare function clearTimeout(handle: any): void;
namespace ts {
export type FileWatcherCallback = (fileName: string, removed?: boolean) => void;
export enum FileWatcherEventKind {
Created,
Changed,
Deleted
}
export type FileWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind) => void;
export type DirectoryWatcherCallback = (fileName: string) => void;
export interface WatchedFile {
fileName: string;
@@ -174,7 +180,7 @@ namespace ts {
const callbacks = fileWatcherCallbacks.get(fileName);
if (callbacks) {
for (const fileCallback of callbacks) {
fileCallback(fileName);
fileCallback(fileName, FileWatcherEventKind.Changed);
}
}
}
@@ -340,11 +346,22 @@ namespace ts {
}
function fileChanged(curr: any, prev: any) {
if (+curr.mtime <= +prev.mtime) {
const isCurrZero = +curr.mtime === 0;
const isPrevZero = +prev.mtime === 0;
const created = !isCurrZero && isPrevZero;
const deleted = isCurrZero && !isPrevZero;
const eventKind = created
? FileWatcherEventKind.Created
: deleted
? FileWatcherEventKind.Deleted
: FileWatcherEventKind.Changed;
if (eventKind === FileWatcherEventKind.Changed && +curr.mtime <= +prev.mtime) {
return;
}
callback(fileName);
callback(fileName, eventKind);
}
},
watchDirectory: (directoryName, callback, recursive) => {
+13 -3
View File
@@ -285,6 +285,16 @@ namespace ts {
setCachedProgram(compileResult.program);
reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes));
const missingPaths = compileResult.program.getMissingFilePaths();
missingPaths.forEach(path => {
const fileWatcher = sys.watchFile(path, (_fileName, eventKind) => {
if (eventKind === FileWatcherEventKind.Created) {
fileWatcher.close();
startTimerForRecompilation();
}
});
});
}
function cachedFileExists(fileName: string): boolean {
@@ -308,7 +318,7 @@ namespace ts {
const sourceFile = hostGetSourceFile(fileName, languageVersion, onError);
if (sourceFile && isWatchSet(compilerOptions) && sys.watchFile) {
// Attach a file watcher
sourceFile.fileWatcher = sys.watchFile(sourceFile.fileName, (_fileName: string, removed?: boolean) => sourceFileChanged(sourceFile, removed));
sourceFile.fileWatcher = sys.watchFile(sourceFile.fileName, (_fileName, eventKind) => sourceFileChanged(sourceFile, eventKind));
}
return sourceFile;
}
@@ -330,10 +340,10 @@ namespace ts {
}
// If a source file changes, mark it as unwatched and start the recompilation timer
function sourceFileChanged(sourceFile: SourceFile, removed?: boolean) {
function sourceFileChanged(sourceFile: SourceFile, eventKind: FileWatcherEventKind) {
sourceFile.fileWatcher.close();
sourceFile.fileWatcher = undefined;
if (removed) {
if (eventKind === FileWatcherEventKind.Deleted) {
unorderedRemoveItem(rootFileNames, sourceFile.fileName);
}
startTimerForRecompilation();
+7
View File
@@ -2426,6 +2426,13 @@ namespace ts {
*/
getSourceFiles(): SourceFile[];
/**
* Get a list of file names that were passed to 'createProgram' or referenced in a
* program source file but could not be located.
*/
/* @internal */
getMissingFilePaths(): Path[];
/**
* Emits the JavaScript and declaration files. If targetSourceFile is not specified, then
* the JavaScript and declaration files will be produced for all the files in this program.
+2 -1
View File
@@ -129,6 +129,7 @@
"./unittests/transform.ts",
"./unittests/customTransforms.ts",
"./unittests/textChanges.ts",
"./unittests/telemetry.ts"
"./unittests/telemetry.ts",
"./unittests/programMissingFiles.ts"
]
}
@@ -75,7 +75,7 @@ namespace ts {
const rootScriptInfo = projectService.getOrCreateScriptInfo(rootFile, /* openedByClient */ true, /*containingProject*/ undefined);
const project = projectService.createInferredProjectWithRootFileIfNecessary(rootScriptInfo);
project.setCompilerOptions({ module: ts.ModuleKind.AMD } );
project.setCompilerOptions({ module: ts.ModuleKind.AMD, noLib: true } );
return {
project,
rootScriptInfo
+3 -3
View File
@@ -208,7 +208,7 @@ namespace ts.projectSystem {
file1Consumer1.content = `let y = 10;`;
host.reloadFS([moduleFile1, file1Consumer1, file1Consumer2, configFile, libFile]);
host.triggerFileWatcherCallback(file1Consumer1.path, /*removed*/ false);
host.triggerFileWatcherCallback(file1Consumer1.path, FileWatcherEventKind.Changed);
session.executeCommand(changeModuleFile1ShapeRequest1);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer2] }]);
@@ -225,7 +225,7 @@ namespace ts.projectSystem {
session.executeCommand(changeModuleFile1ShapeRequest1);
// Delete file1Consumer2
host.reloadFS([moduleFile1, file1Consumer1, configFile, libFile]);
host.triggerFileWatcherCallback(file1Consumer2.path, /*removed*/ true);
host.triggerFileWatcherCallback(file1Consumer2.path, FileWatcherEventKind.Deleted);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]);
});
@@ -475,7 +475,7 @@ namespace ts.projectSystem {
openFilesForSession([referenceFile1], session);
host.reloadFS([referenceFile1, configFile]);
host.triggerFileWatcherCallback(moduleFile1.path, /*removed*/ true);
host.triggerFileWatcherCallback(moduleFile1.path, FileWatcherEventKind.Deleted);
const request = makeSessionRequest<server.protocol.FileRequestArgs>(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path });
sendAffectedFileRequestAndCheckResult(session, request, [
@@ -0,0 +1,103 @@
/// <reference path="..\harness.ts" />
namespace ts {
describe("Program.getMissingFilePaths", () => {
const options: CompilerOptions = {
noLib: true,
};
const emptyFileName = "empty.ts";
const emptyFileRelativePath = "./" + emptyFileName;
const emptyFile: Harness.Compiler.TestFile = {
unitName: emptyFileName,
content: ""
};
const referenceFileName = "reference.ts";
const referenceFileRelativePath = "./" + referenceFileName;
const referenceFile: Harness.Compiler.TestFile = {
unitName: referenceFileName,
content:
"/// <reference path=\"d:/imaginary/nonexistent1.ts\"/>\n" + // Absolute
"/// <reference path=\"./nonexistent2.ts\"/>\n" + // Relative
"/// <reference path=\"nonexistent3.ts\"/>\n" + // Unqualified
"/// <reference path=\"nonexistent4\"/>\n" // No extension
};
const testCompilerHost = Harness.Compiler.createCompilerHost(
/*inputFiles*/ [emptyFile, referenceFile],
/*writeFile*/ undefined,
/*scriptTarget*/ undefined,
/*useCaseSensitiveFileNames*/ false,
/*currentDirectory*/ "d:\\pretend\\",
/*newLineKind*/ NewLineKind.LineFeed,
/*libFiles*/ undefined
);
it("handles no missing root files", () => {
const program = createProgram([emptyFileRelativePath], options, testCompilerHost);
const missing = program.getMissingFilePaths();
assert.isDefined(missing);
assert.deepEqual(missing, []);
});
it("handles missing root file", () => {
const program = createProgram(["./nonexistent.ts"], options, testCompilerHost);
const missing = program.getMissingFilePaths();
assert.isDefined(missing);
assert.deepEqual(missing, ["d:/pretend/nonexistent.ts"]); // Absolute path
});
it("handles multiple missing root files", () => {
const program = createProgram(["./nonexistent0.ts", "./nonexistent1.ts"], options, testCompilerHost);
const missing = program.getMissingFilePaths().sort();
assert.deepEqual(missing, ["d:/pretend/nonexistent0.ts", "d:/pretend/nonexistent1.ts"]);
});
it("handles a mix of present and missing root files", () => {
const program = createProgram(["./nonexistent0.ts", emptyFileRelativePath, "./nonexistent1.ts"], options, testCompilerHost);
const missing = program.getMissingFilePaths().sort();
assert.deepEqual(missing, ["d:/pretend/nonexistent0.ts", "d:/pretend/nonexistent1.ts"]);
});
it("handles repeatedly specified root files", () => {
const program = createProgram(["./nonexistent.ts", "./nonexistent.ts"], options, testCompilerHost);
const missing = program.getMissingFilePaths();
assert.isDefined(missing);
assert.deepEqual(missing, ["d:/pretend/nonexistent.ts"]);
});
it("normalizes file paths", () => {
const program0 = createProgram(["./nonexistent.ts", "./NONEXISTENT.ts"], options, testCompilerHost);
const program1 = createProgram(["./NONEXISTENT.ts", "./nonexistent.ts"], options, testCompilerHost);
const missing0 = program0.getMissingFilePaths();
const missing1 = program1.getMissingFilePaths();
assert.equal(missing0.length, 1);
assert.deepEqual(missing0, missing1);
});
it("handles missing triple slash references", () => {
const program = createProgram([referenceFileRelativePath], options, testCompilerHost);
const missing = program.getMissingFilePaths().sort();
assert.isDefined(missing);
assert.deepEqual(missing, [
// From absolute reference
"d:/imaginary/nonexistent1.ts",
// From relative reference
"d:/pretend/nonexistent2.ts",
// From unqualified reference
"d:/pretend/nonexistent3.ts",
// From no-extension reference
"d:/pretend/nonexistent4.d.ts",
"d:/pretend/nonexistent4.ts",
"d:/pretend/nonexistent4.tsx"
]);
});
});
}
+2 -2
View File
@@ -135,7 +135,7 @@ namespace ts.projectSystem {
}
// fix config and trigger watcher
host.reloadFS([file1, file2, correctConfig]);
host.triggerFileWatcherCallback(correctConfig.path, /*false*/);
host.triggerFileWatcherCallback(correctConfig.path, FileWatcherEventKind.Changed);
{
projectService.checkNumberOfProjects({ configuredProjects: 1 });
const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f);
@@ -177,7 +177,7 @@ namespace ts.projectSystem {
}
// break config and trigger watcher
host.reloadFS([file1, file2, corruptedConfig]);
host.triggerFileWatcherCallback(corruptedConfig.path, /*false*/);
host.triggerFileWatcherCallback(corruptedConfig.path, FileWatcherEventKind.Changed);
{
projectService.checkNumberOfProjects({ configuredProjects: 1 });
const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f);
@@ -316,6 +316,31 @@ namespace ts {
assert.isTrue(program_1.structureIsReused === StructureIsReused.Not);
});
it("succeeds if missing files remain missing", () => {
const options: CompilerOptions = { target, noLib: true };
const program_1 = newProgram(files, ["a.ts"], options);
assert.notDeepEqual(emptyArray, program_1.getMissingFilePaths());
const program_2 = updateProgram(program_1, ["a.ts"], options, noop);
assert.deepEqual(program_1.getMissingFilePaths(), program_2.getMissingFilePaths());
assert.equal(StructureIsReused.Completely, program_1.structureIsReused);
});
it("fails if missing file is created", () => {
const options: CompilerOptions = { target, noLib: true };
const program_1 = newProgram(files, ["a.ts"], options);
assert.notDeepEqual(emptyArray, program_1.getMissingFilePaths());
const newTexts: NamedSourceText[] = files.concat([{ name: "non-existing-file.ts", text: SourceText.New("", "", `var x = 1`) }]);
const program_2 = updateProgram(program_1, ["a.ts"], options, noop, newTexts);
assert.deepEqual(emptyArray, program_2.getMissingFilePaths());
assert.equal(StructureIsReused.SafeModules, program_1.structureIsReused);
});
it("resolution cache follows imports", () => {
(<any>Error).stackTraceLimit = Infinity;
+21 -21
View File
@@ -484,12 +484,12 @@ namespace ts.projectSystem {
}
}
triggerFileWatcherCallback(fileName: string, removed?: boolean): void {
triggerFileWatcherCallback(fileName: string, eventKind: FileWatcherEventKind): void {
const path = this.toPath(fileName);
const callbacks = this.watchedFiles.get(path);
if (callbacks) {
for (const callback of callbacks) {
callback(path, removed);
callback(path, eventKind);
}
}
}
@@ -770,7 +770,7 @@ namespace ts.projectSystem {
// remove the tsconfig file
host.reloadFS(filesWithoutConfig);
host.triggerFileWatcherCallback(configFile.path);
host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Changed);
checkNumberOfInferredProjects(projectService, 2);
checkNumberOfConfiguredProjects(projectService, 0);
@@ -927,7 +927,7 @@ namespace ts.projectSystem {
"files": ["${commonFile1.path}"]
}`;
host.reloadFS(files);
host.triggerFileWatcherCallback(configFile.path);
host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Changed);
checkNumberOfConfiguredProjects(projectService, 1);
checkProjectRootFiles(project, [commonFile1.path]);
@@ -1001,7 +1001,7 @@ namespace ts.projectSystem {
"files": ["${file1.path}"]
}`;
host.reloadFS(files);
host.triggerFileWatcherCallback(configFile.path);
host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Changed);
checkProjectActualFiles(project, [file1.path, classicModuleFile.path, configFile.path]);
checkNumberOfInferredProjects(projectService, 1);
});
@@ -1432,7 +1432,7 @@ namespace ts.projectSystem {
};
host.reloadFS([file1, modifiedFile2, file3]);
host.triggerFileWatcherCallback(modifiedFile2.path, /*removed*/ false);
host.triggerFileWatcherCallback(modifiedFile2.path, FileWatcherEventKind.Changed);
checkNumberOfInferredProjects(projectService, 1);
checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, modifiedFile2.path, file3.path]);
@@ -1464,7 +1464,7 @@ namespace ts.projectSystem {
checkNumberOfProjects(projectService, { inferredProjects: 1 });
host.reloadFS([file1, file3]);
host.triggerFileWatcherCallback(file2.path, /*removed*/ true);
host.triggerFileWatcherCallback(file2.path, FileWatcherEventKind.Deleted);
checkNumberOfProjects(projectService, { inferredProjects: 2 });
@@ -1662,7 +1662,7 @@ namespace ts.projectSystem {
};
host.reloadFS([file1, file2, modifiedConfigFile]);
host.triggerFileWatcherCallback(configFile.path, /*removed*/ false);
host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Changed);
checkNumberOfProjects(projectService, { configuredProjects: 1 });
checkProjectRootFiles(projectService.configuredProjects[0], [file1.path, file2.path]);
@@ -1695,7 +1695,7 @@ namespace ts.projectSystem {
};
host.reloadFS([file1, file2, modifiedConfigFile]);
host.triggerFileWatcherCallback(configFile.path, /*removed*/ false);
host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Changed);
checkNumberOfProjects(projectService, { configuredProjects: 1 });
checkProjectRootFiles(projectService.configuredProjects[0], [file1.path, file2.path]);
@@ -1775,7 +1775,7 @@ namespace ts.projectSystem {
checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path, config.path]);
host.reloadFS([file1, file2]);
host.triggerFileWatcherCallback(config.path, /*removed*/ true);
host.triggerFileWatcherCallback(config.path, FileWatcherEventKind.Deleted);
checkNumberOfProjects(projectService, { inferredProjects: 2 });
checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]);
@@ -2027,7 +2027,7 @@ namespace ts.projectSystem {
projectService.openExternalProject({ projectFileName, options: {}, rootFiles: [{ fileName: file1.path, scriptKind: ScriptKind.JS, hasMixedContent: true }] });
checkNumberOfProjects(projectService, { externalProjects: 1 });
checkWatchedFiles(host, []);
checkWatchedFiles(host, [libFile.path]); // watching the "missing" lib file
const project = projectService.externalProjects[0];
@@ -2257,7 +2257,7 @@ namespace ts.projectSystem {
assert.isFalse(lastEvent.data.languageServiceEnabled, "Language service state");
host.reloadFS([f1, f2, configWithExclude]);
host.triggerFileWatcherCallback(config.path, /*removed*/ false);
host.triggerFileWatcherCallback(config.path, FileWatcherEventKind.Changed);
checkNumberOfProjects(projectService, { configuredProjects: 1 });
assert.isTrue(project.languageServiceEnabled, "Language service enabled");
@@ -2491,7 +2491,7 @@ namespace ts.projectSystem {
// rename tsconfig.json back to lib.ts
host.reloadFS([f1, f2]);
host.triggerFileWatcherCallback(tsconfig.path, /*removed*/ true);
host.triggerFileWatcherCallback(tsconfig.path, FileWatcherEventKind.Deleted);
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, f2.path]),
@@ -2636,7 +2636,7 @@ namespace ts.projectSystem {
checkProjectActualFiles(projectService.configuredProjects[0], [libES5.path, app.path, config1.path]);
host.reloadFS([libES5, libES2015Promise, app, config2]);
host.triggerFileWatcherCallback(config1.path);
host.triggerFileWatcherCallback(config1.path, FileWatcherEventKind.Changed);
projectService.checkNumberOfProjects({ configuredProjects: 1 });
checkProjectActualFiles(projectService.configuredProjects[0], [libES5.path, libES2015Promise.path, app.path, config2.path]);
@@ -2859,7 +2859,7 @@ namespace ts.projectSystem {
const moduleFileNewPath = "/a/b/moduleFile1.ts";
moduleFile.path = moduleFileNewPath;
host.reloadFS([moduleFile, file1]);
host.triggerFileWatcherCallback(moduleFileOldPath);
host.triggerFileWatcherCallback(moduleFileOldPath, FileWatcherEventKind.Changed);
host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path);
host.runQueuedTimeoutCallbacks();
diags = <server.protocol.Diagnostic[]>session.executeCommand(getErrRequest).response;
@@ -2867,7 +2867,7 @@ namespace ts.projectSystem {
moduleFile.path = moduleFileOldPath;
host.reloadFS([moduleFile, file1]);
host.triggerFileWatcherCallback(moduleFileNewPath);
host.triggerFileWatcherCallback(moduleFileNewPath, FileWatcherEventKind.Changed);
host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path);
host.runQueuedTimeoutCallbacks();
@@ -2911,7 +2911,7 @@ namespace ts.projectSystem {
const moduleFileNewPath = "/a/b/moduleFile1.ts";
moduleFile.path = moduleFileNewPath;
host.reloadFS([moduleFile, file1, configFile]);
host.triggerFileWatcherCallback(moduleFileOldPath);
host.triggerFileWatcherCallback(moduleFileOldPath, FileWatcherEventKind.Changed);
host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path);
host.runQueuedTimeoutCallbacks();
diags = <server.protocol.Diagnostic[]>session.executeCommand(getErrRequest).response;
@@ -2919,7 +2919,7 @@ namespace ts.projectSystem {
moduleFile.path = moduleFileOldPath;
host.reloadFS([moduleFile, file1, configFile]);
host.triggerFileWatcherCallback(moduleFileNewPath);
host.triggerFileWatcherCallback(moduleFileNewPath, FileWatcherEventKind.Changed);
host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path);
host.runQueuedTimeoutCallbacks();
diags = <server.protocol.Diagnostic[]>session.executeCommand(getErrRequest).response;
@@ -3084,7 +3084,7 @@ namespace ts.projectSystem {
}
}`;
host.reloadFS([file, configFile]);
host.triggerFileWatcherCallback(configFile.path);
host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Changed);
host.runQueuedTimeoutCallbacks();
serverEventManager.checkEventCountOfType("configFileDiag", 2);
@@ -3092,7 +3092,7 @@ namespace ts.projectSystem {
"compilerOptions": {}
}`;
host.reloadFS([file, configFile]);
host.triggerFileWatcherCallback(configFile.path);
host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Changed);
host.runQueuedTimeoutCallbacks();
serverEventManager.checkEventCountOfType("configFileDiag", 3);
});
@@ -4020,7 +4020,7 @@ namespace ts.projectSystem {
configFile.content = configFileContentWithoutCommentLine;
host.reloadFS([file, configFile]);
host.triggerFileWatcherCallback(configFile.path);
host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Changed);
const diagsAfterEdit = session.executeCommand(<server.protocol.SemanticDiagnosticsSyncRequest>{
type: "request",
+2 -2
View File
@@ -737,7 +737,7 @@ namespace ts.projectSystem {
checkNumberOfProjects(projectService, { configuredProjects: 1 });
const p = projectService.configuredProjects[0];
checkProjectActualFiles(p, [app.path, jsconfig.path]);
checkWatchedFiles(host, [jsconfig.path, "/bower_components", "/node_modules"]);
checkWatchedFiles(host, [jsconfig.path, "/bower_components", "/node_modules", libFile.path]);
installer.installAll(/*expectedCount*/ 1);
@@ -826,7 +826,7 @@ namespace ts.projectSystem {
installer.checkPendingCommands(/*expectedCount*/ 0);
host.reloadFS([f, fixedPackageJson]);
host.triggerFileWatcherCallback(fixedPackageJson.path, /*removed*/ false);
host.triggerFileWatcherCallback(fixedPackageJson.path, FileWatcherEventKind.Changed);
// expected install request
installer.installAll(/*expectedCount*/ 1);
+5 -2
View File
@@ -210,8 +210,11 @@ namespace ts.server {
return this.host.resolvePath(path);
}
fileExists(path: string): boolean {
return this.host.fileExists(path);
fileExists(file: string): boolean {
// As an optimization, don't hit the disks for files we already know don't exist
// (because we're watching for their creation).
const path = toPath(file, this.host.getCurrentDirectory(), this.getCanonicalFileName);
return !this.project.isWatchedMissingFile(path) && this.host.fileExists(file);
}
readFile(fileName: string): string {
+41 -3
View File
@@ -107,6 +107,7 @@ namespace ts.server {
private rootFilesMap: Map<ScriptInfo> = createMap<ScriptInfo>();
private program: ts.Program;
private externalFiles: SortedReadonlyArray<string>;
private missingFilesMap: Map<FileWatcher> = createMap<FileWatcher>();
private cachedUnresolvedImportsPerFile = new UnresolvedImportsMap();
private lastCachedUnresolvedImportsList: SortedReadonlyArray<string>;
@@ -310,6 +311,10 @@ namespace ts.server {
this.lsHost.dispose();
this.lsHost = undefined;
// Clean up file watchers waiting for missing files
this.missingFilesMap.forEach(fileWatcher => fileWatcher.close());
this.missingFilesMap = undefined;
// signal language service to release source files acquired from document registry
this.languageService.dispose();
this.languageService = undefined;
@@ -586,12 +591,12 @@ namespace ts.server {
const oldProgram = this.program;
this.program = this.languageService.getProgram();
let hasChanges = false;
// bump up the version if
// - oldProgram is not set - this is a first time updateGraph is called
// - newProgram is different from the old program and structure of the old program was not reused.
if (!oldProgram || (this.program !== oldProgram && !(oldProgram.structureIsReused & StructureIsReused.Completely))) {
hasChanges = true;
const hasChanges = !oldProgram || (this.program !== oldProgram && !(oldProgram.structureIsReused & StructureIsReused.Completely));
if (hasChanges) {
if (oldProgram) {
for (const f of oldProgram.getSourceFiles()) {
if (this.program.getSourceFileByPath(f.path)) {
@@ -604,6 +609,35 @@ namespace ts.server {
}
}
}
const missingFilePaths = this.program.getMissingFilePaths();
const missingFilePathsSet = arrayToSet(missingFilePaths, p => p);
// Files that are no longer missing (e.g. because they are no longer required)
// should no longer be watched.
this.missingFilesMap.forEach((fileWatcher, missingFilePath) => {
if (!missingFilePathsSet.has(missingFilePath)) {
this.missingFilesMap.delete(missingFilePath);
fileWatcher.close();
}
});
// Missing files that are not yet watched should be added to the map.
for (const missingFilePath of missingFilePaths) {
if (!this.missingFilesMap.has(missingFilePath)) {
const fileWatcher = this.projectService.host.watchFile(missingFilePath, (_filename: string, eventKind: FileWatcherEventKind) => {
if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) {
fileWatcher.close();
this.missingFilesMap.delete(missingFilePath);
// When a missing file is created, we should update the graph.
this.markAsDirty();
this.updateGraph();
}
});
this.missingFilesMap.set(missingFilePath, fileWatcher);
}
}
}
const oldExternalFiles = this.externalFiles || emptyArray as SortedReadonlyArray<string>;
@@ -626,6 +660,10 @@ namespace ts.server {
return hasChanges;
}
isWatchedMissingFile(path: Path) {
return this.missingFilesMap.has(path);
}
getScriptInfoLSHost(fileName: string) {
const scriptInfo = this.projectService.getOrCreateScriptInfo(fileName, /*openedByClient*/ false);
if (scriptInfo) {
+8 -3
View File
@@ -523,11 +523,14 @@ namespace ts.server {
fs.stat(watchedFile.fileName, (err: any, stats: any) => {
if (err) {
watchedFile.callback(watchedFile.fileName);
watchedFile.callback(watchedFile.fileName, FileWatcherEventKind.Changed);
}
else if (watchedFile.mtime.getTime() !== stats.mtime.getTime()) {
watchedFile.mtime = stats.mtime;
watchedFile.callback(watchedFile.fileName, watchedFile.mtime.getTime() === 0);
const eventKind = watchedFile.mtime.getTime() === 0
? FileWatcherEventKind.Deleted
: FileWatcherEventKind.Changed;
watchedFile.callback(watchedFile.fileName, eventKind);
}
});
}
@@ -559,7 +562,9 @@ namespace ts.server {
const file: WatchedFile = {
fileName,
callback,
mtime: getModifiedTime(fileName)
mtime: sys.fileExists(fileName)
? getModifiedTime(fileName)
: new Date(0) // Any subsequent modification will occur after this time
};
watchedFiles.push(file);
+4 -1
View File
@@ -2,7 +2,10 @@
namespace ts.DocumentHighlights {
export function getDocumentHighlights(program: Program, cancellationToken: CancellationToken, sourceFile: SourceFile, position: number, sourceFilesToSearch: SourceFile[]): DocumentHighlights[] | undefined {
const node = getTouchingWord(sourceFile, position, /*includeJsDocComment*/ true);
if (!node) return undefined;
// Note that getTouchingWord indicates failure by returning the sourceFile node.
if (node === sourceFile) return undefined;
Debug.assert(node.parent !== undefined);
if (isJsxOpeningElement(node.parent) && node.parent.tagName === node || isJsxClosingElement(node.parent)) {
// For a JSX element, just highlight the matching tag, not all references.