Restructure updating the configured project from disk and actual project update

Also reload the projects when extra extension in the host change
This commit is contained in:
Sheetal Nandi 2017-06-30 18:04:33 -07:00
parent 21ad26b6ff
commit ae33ae894d
3 changed files with 63 additions and 34 deletions

View file

@ -770,7 +770,7 @@ namespace ts.projectSystem {
// remove the tsconfig file // remove the tsconfig file
host.reloadFS(filesWithoutConfig); host.reloadFS(filesWithoutConfig);
host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Changed); host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Deleted);
checkNumberOfInferredProjects(projectService, 2); checkNumberOfInferredProjects(projectService, 2);
checkNumberOfConfiguredProjects(projectService, 0); checkNumberOfConfiguredProjects(projectService, 0);
@ -1837,16 +1837,18 @@ namespace ts.projectSystem {
// HTML file will not be included in any projects yet // HTML file will not be included in any projects yet
checkNumberOfProjects(projectService, { configuredProjects: 1 }); checkNumberOfProjects(projectService, { configuredProjects: 1 });
checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, config.path]); const configuredProj = projectService.configuredProjects[0];
checkProjectActualFiles(configuredProj, [file1.path, config.path]);
// Specify .html extension as mixed content // Specify .html extension as mixed content
const extraFileExtensions = [{ extension: ".html", scriptKind: ScriptKind.JS, isMixedContent: true }]; const extraFileExtensions = [{ extension: ".html", scriptKind: ScriptKind.JS, isMixedContent: true }];
const configureHostRequest = makeSessionRequest<protocol.ConfigureRequestArguments>(CommandNames.Configure, { extraFileExtensions }); const configureHostRequest = makeSessionRequest<protocol.ConfigureRequestArguments>(CommandNames.Configure, { extraFileExtensions });
session.executeCommand(configureHostRequest).response; session.executeCommand(configureHostRequest).response;
// HTML file still not included in the project as it is closed // The configured project should now be updated to include html file
checkNumberOfProjects(projectService, { configuredProjects: 1 }); checkNumberOfProjects(projectService, { configuredProjects: 1 });
checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, config.path]); assert.strictEqual(projectService.configuredProjects[0], configuredProj, "Same configured project should be updated");
checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path, config.path]);
// Open HTML file // Open HTML file
projectService.applyChangesInOpenFiles( projectService.applyChangesInOpenFiles(
@ -2859,7 +2861,7 @@ namespace ts.projectSystem {
const moduleFileNewPath = "/a/b/moduleFile1.ts"; const moduleFileNewPath = "/a/b/moduleFile1.ts";
moduleFile.path = moduleFileNewPath; moduleFile.path = moduleFileNewPath;
host.reloadFS([moduleFile, file1]); host.reloadFS([moduleFile, file1]);
host.triggerFileWatcherCallback(moduleFileOldPath, FileWatcherEventKind.Changed); host.triggerFileWatcherCallback(moduleFileOldPath, FileWatcherEventKind.Deleted);
host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path); host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path);
host.runQueuedTimeoutCallbacks(); host.runQueuedTimeoutCallbacks();
diags = <server.protocol.Diagnostic[]>session.executeCommand(getErrRequest).response; diags = <server.protocol.Diagnostic[]>session.executeCommand(getErrRequest).response;
@ -2911,7 +2913,8 @@ namespace ts.projectSystem {
const moduleFileNewPath = "/a/b/moduleFile1.ts"; const moduleFileNewPath = "/a/b/moduleFile1.ts";
moduleFile.path = moduleFileNewPath; moduleFile.path = moduleFileNewPath;
host.reloadFS([moduleFile, file1, configFile]); host.reloadFS([moduleFile, file1, configFile]);
host.triggerFileWatcherCallback(moduleFileOldPath, FileWatcherEventKind.Changed); host.triggerFileWatcherCallback(moduleFileOldPath, FileWatcherEventKind.Deleted);
host.triggerFileWatcherCallback(moduleFileNewPath, FileWatcherEventKind.Created);
host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path); host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path);
host.runQueuedTimeoutCallbacks(); host.runQueuedTimeoutCallbacks();
diags = <server.protocol.Diagnostic[]>session.executeCommand(getErrRequest).response; diags = <server.protocol.Diagnostic[]>session.executeCommand(getErrRequest).response;
@ -2919,7 +2922,8 @@ namespace ts.projectSystem {
moduleFile.path = moduleFileOldPath; moduleFile.path = moduleFileOldPath;
host.reloadFS([moduleFile, file1, configFile]); host.reloadFS([moduleFile, file1, configFile]);
host.triggerFileWatcherCallback(moduleFileNewPath, FileWatcherEventKind.Changed); host.triggerFileWatcherCallback(moduleFileNewPath, FileWatcherEventKind.Deleted);
host.triggerFileWatcherCallback(moduleFileOldPath, FileWatcherEventKind.Created);
host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path); host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path);
host.runQueuedTimeoutCallbacks(); host.runQueuedTimeoutCallbacks();
diags = <server.protocol.Diagnostic[]>session.executeCommand(getErrRequest).response; diags = <server.protocol.Diagnostic[]>session.executeCommand(getErrRequest).response;

View file

@ -538,21 +538,21 @@ namespace ts.server {
} }
} }
private onSourceFileChanged(fileName: NormalizedPath) { private onSourceFileChanged(fileName: NormalizedPath, eventKind: FileWatcherEventKind) {
const info = this.getScriptInfoForNormalizedPath(fileName); const info = this.getScriptInfoForNormalizedPath(fileName);
if (!info) { if (!info) {
this.logger.info(`Error: got watch notification for unknown file: ${fileName}`); this.logger.info(`Error: got watch notification for unknown file: ${fileName}`);
return; return;
} }
if (!this.host.fileExists(fileName)) { if (eventKind === FileWatcherEventKind.Deleted) {
// File was deleted // File was deleted
this.handleDeletedFile(info); this.handleDeletedFile(info);
} }
else { else {
if (info && (!info.isScriptOpen())) { if (!info.isScriptOpen()) {
if (info.containingProjects.length === 0) { if (info.containingProjects.length === 0) {
// Orphan script info, remove it as we can always reload it on next open // Orphan script info, remove it as we can always reload it on next open file request
info.stopWatcher(); info.stopWatcher();
this.filenameToScriptInfo.delete(info.path); this.filenameToScriptInfo.delete(info.path);
} }
@ -605,7 +605,7 @@ namespace ts.server {
this.logger.info(`Type root file ${fileName} changed`); this.logger.info(`Type root file ${fileName} changed`);
this.throttledOperations.schedule(project.getConfigFilePath() + " * type root", /*delay*/ 250, () => { this.throttledOperations.schedule(project.getConfigFilePath() + " * type root", /*delay*/ 250, () => {
project.updateTypes(); project.updateTypes();
this.updateConfiguredProject(project); // TODO: Figure out why this is needed (should be redundant?) this.reloadConfiguredProject(project); // TODO: Figure out why this is needed (should be redundant?)
this.refreshInferredProjects(); this.refreshInferredProjects();
}); });
} }
@ -644,7 +644,7 @@ namespace ts.server {
// just update the current project. // just update the current project.
this.logger.info("Updating configured project"); this.logger.info("Updating configured project");
this.updateConfiguredProject(project); this.updateConfiguredProject(project, projectOptions, configFileErrors);
// Call refreshInferredProjects to clean up inferred projects we may have // Call refreshInferredProjects to clean up inferred projects we may have
// created for the new files // created for the new files
@ -652,11 +652,19 @@ namespace ts.server {
} }
} }
private onConfigChangedForConfiguredProject(project: ConfiguredProject) { private onConfigChangedForConfiguredProject(project: ConfiguredProject, eventKind: FileWatcherEventKind) {
const configFileName = project.getConfigFilePath(); const configFileName = project.getConfigFilePath();
this.logger.info(`Config file changed: ${configFileName}`); if (eventKind === FileWatcherEventKind.Deleted) {
const configFileErrors = this.updateConfiguredProject(project); this.logger.info(`Config file deleted: ${configFileName}`);
this.reportConfigFileDiagnostics(configFileName, configFileErrors, /*triggerFile*/ configFileName); // TODO: (sheetalkamat) Get the list of open files from this project
// and update the projects
this.removeProject(project);
}
else {
this.logger.info(`Config file changed: ${configFileName}`);
this.reloadConfiguredProject(project);
this.reportConfigFileDiagnostics(configFileName, project.getGlobalProjectErrors(), /*triggerFile*/ configFileName);
}
this.refreshInferredProjects(); this.refreshInferredProjects();
} }
@ -670,6 +678,8 @@ namespace ts.server {
return; return;
} }
// TODO: (sheetalkamat) Technically we shouldnt have to do this:
// We can report these errors in reload projects or after reload project?
const { configFileErrors } = this.convertConfigFileContentToProjectOptions(fileName); const { configFileErrors } = this.convertConfigFileContentToProjectOptions(fileName);
this.reportConfigFileDiagnostics(fileName, configFileErrors, fileName); this.reportConfigFileDiagnostics(fileName, configFileErrors, fileName);
@ -883,7 +893,7 @@ namespace ts.server {
this.openConfigFile(configFileName, fileName); this.openConfigFile(configFileName, fileName);
} }
else { else {
this.updateConfiguredProject(project); this.reloadConfiguredProject(project);
} }
} }
} }
@ -1133,7 +1143,7 @@ namespace ts.server {
this.addFilesToProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typeAcquisition, configFileErrors); this.addFilesToProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typeAcquisition, configFileErrors);
project.watchConfigFile(project => this.onConfigChangedForConfiguredProject(project)); project.watchConfigFile((project, eventKind) => this.onConfigChangedForConfiguredProject(project, eventKind));
if (!sizeLimitExceeded) { if (!sizeLimitExceeded) {
this.watchConfigDirectoryForProject(project, projectOptions); this.watchConfigDirectoryForProject(project, projectOptions);
} }
@ -1161,6 +1171,9 @@ namespace ts.server {
project.addRoot(info); project.addRoot(info);
} }
else { else {
// TODO: (sheetalkamat) because the files are not added as a root, we wont have these available in
// missing files unless someone recreates the project or it was also refrenced in existing sourcefile
// Also these errors wouldnt show correct errors
(configFileErrors || (configFileErrors = [])).push(createFileNotFoundDiagnostic(rootFilename)); (configFileErrors || (configFileErrors = [])).push(createFileNotFoundDiagnostic(rootFilename));
} }
} }
@ -1183,12 +1196,11 @@ namespace ts.server {
const newRootScriptInfos: ScriptInfo[] = []; const newRootScriptInfos: ScriptInfo[] = [];
const newRootScriptInfoMap: NormalizedPathMap<ScriptInfo> = createNormalizedPathMap<ScriptInfo>(); const newRootScriptInfoMap: NormalizedPathMap<ScriptInfo> = createNormalizedPathMap<ScriptInfo>();
let projectErrors: Diagnostic[];
let rootFilesChanged = false; let rootFilesChanged = false;
for (const f of newUncheckedFiles) { for (const f of newUncheckedFiles) {
const newRootFile = propertyReader.getFileName(f); const newRootFile = propertyReader.getFileName(f);
if (!this.host.fileExists(newRootFile)) { if (!this.host.fileExists(newRootFile)) {
(projectErrors || (projectErrors = [])).push(createFileNotFoundDiagnostic(newRootFile)); (configFileErrors || (configFileErrors = [])).push(createFileNotFoundDiagnostic(newRootFile));
continue; continue;
} }
const normalizedPath = toNormalizedPath(newRootFile); const normalizedPath = toNormalizedPath(newRootFile);
@ -1247,17 +1259,20 @@ namespace ts.server {
if (compileOnSave !== undefined) { if (compileOnSave !== undefined) {
project.compileOnSaveEnabled = compileOnSave; project.compileOnSaveEnabled = compileOnSave;
} }
project.setProjectErrors(concatenate(configFileErrors, projectErrors)); project.setProjectErrors(configFileErrors);
project.updateGraph(); project.updateGraph();
} }
private updateConfiguredProject(project: ConfiguredProject) { /**
if (!this.host.fileExists(project.getConfigFilePath())) { * Read the config file of the project again and update the project
this.logger.info("Config file deleted"); * @param project
this.removeProject(project); */
return; private reloadConfiguredProject(project: ConfiguredProject) {
} // At this point, there is no reason to not have configFile in the host
// TODO: (sheetalkamat) configErrors should always be in project and there shouldnt be
// any need to return these errors
// note: the returned "success" is true does not mean the "configFileErrors" is empty. // note: the returned "success" is true does not mean the "configFileErrors" is empty.
// because we might have tolerated the errors and kept going. So always return the configFileErrors // because we might have tolerated the errors and kept going. So always return the configFileErrors
@ -1266,9 +1281,16 @@ namespace ts.server {
if (!success) { if (!success) {
// reset project settings to default // reset project settings to default
this.updateNonInferredProject(project, [], fileNamePropertyReader, {}, {}, /*compileOnSave*/ false, configFileErrors); this.updateNonInferredProject(project, [], fileNamePropertyReader, {}, {}, /*compileOnSave*/ false, configFileErrors);
return configFileErrors;
} }
return this.updateConfiguredProject(project, projectOptions, configFileErrors);
}
/**
* Updates the configured project with updated config file contents
* @param project
*/
private updateConfiguredProject(project: ConfiguredProject, projectOptions: ProjectOptions, configFileErrors: Diagnostic[]) {
if (this.exceededTotalSizeLimitForNonTsFiles(project.canonicalConfigFilePath, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader)) { if (this.exceededTotalSizeLimitForNonTsFiles(project.canonicalConfigFilePath, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader)) {
project.setCompilerOptions(projectOptions.compilerOptions); project.setCompilerOptions(projectOptions.compilerOptions);
if (!project.languageServiceEnabled) { if (!project.languageServiceEnabled) {
@ -1277,13 +1299,13 @@ namespace ts.server {
} }
project.disableLanguageService(); project.disableLanguageService();
project.stopWatchingDirectory(); project.stopWatchingDirectory();
project.setProjectErrors(configFileErrors);
} }
else { else {
project.enableLanguageService(); project.enableLanguageService();
this.watchConfigDirectoryForProject(project, projectOptions); this.watchConfigDirectoryForProject(project, projectOptions);
this.updateNonInferredProject(project, projectOptions.files, fileNamePropertyReader, projectOptions.compilerOptions, projectOptions.typeAcquisition, projectOptions.compileOnSave, configFileErrors); this.updateNonInferredProject(project, projectOptions.files, fileNamePropertyReader, projectOptions.compilerOptions, projectOptions.typeAcquisition, projectOptions.compileOnSave, configFileErrors);
} }
return configFileErrors;
} }
createInferredProjectWithRootFileIfNecessary(root: ScriptInfo) { createInferredProjectWithRootFileIfNecessary(root: ScriptInfo) {
@ -1324,7 +1346,7 @@ namespace ts.server {
// do not watch files with mixed content - server doesn't know how to interpret it // do not watch files with mixed content - server doesn't know how to interpret it
if (!info.hasMixedContent) { if (!info.hasMixedContent) {
const { fileName } = info; const { fileName } = info;
info.setWatcher(this.host.watchFile(fileName, _ => this.onSourceFileChanged(fileName))); info.setWatcher(this.host.watchFile(fileName, (_fileName, eventKind) => this.onSourceFileChanged(fileName, eventKind)));
} }
} }
@ -1390,8 +1412,9 @@ namespace ts.server {
} }
if (args.extraFileExtensions) { if (args.extraFileExtensions) {
this.hostConfiguration.extraFileExtensions = args.extraFileExtensions; this.hostConfiguration.extraFileExtensions = args.extraFileExtensions;
// TODO: (sheetalkamat) We need to update the projects because of we might interprete more/less files // We need to update the projects because of we might interprete more/less files
// depending on whether extra files extenstions are either added or removed // depending on whether extra files extenstions are either added or removed
this.reloadProjects();
this.logger.info("Host file extension mappings updated"); this.logger.info("Host file extension mappings updated");
} }
} }
@ -1408,6 +1431,8 @@ namespace ts.server {
this.logger.info("reload projects."); this.logger.info("reload projects.");
// try to reload config file for all open files // try to reload config file for all open files
for (const info of this.openFiles) { for (const info of this.openFiles) {
// TODO: (sheetalkamat) batch these = multiple open files from the same project will
// end up updating project that many times
this.openOrUpdateConfiguredProjectForFile(info.fileName); this.openOrUpdateConfiguredProjectForFile(info.fileName);
} }
this.refreshInferredProjects(); this.refreshInferredProjects();

View file

@ -1072,8 +1072,8 @@ namespace ts.server {
})); }));
} }
watchConfigFile(callback: (project: ConfiguredProject) => void) { watchConfigFile(callback: (project: ConfiguredProject, eventKind: FileWatcherEventKind) => void) {
this.projectFileWatcher = this.projectService.host.watchFile(this.getConfigFilePath(), _ => callback(this)); this.projectFileWatcher = this.projectService.host.watchFile(this.getConfigFilePath(), (_fileName, eventKind) => callback(this, eventKind));
} }
watchTypeRoots(callback: (project: ConfiguredProject, path: string) => void) { watchTypeRoots(callback: (project: ConfiguredProject, path: string) => void) {