mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-15 20:25:23 -06:00
More updates per PR feedback
This commit is contained in:
parent
02b8a7de65
commit
f723beb244
@ -534,6 +534,19 @@ namespace ts {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function mapDefinedIter<T, U>(iter: Iterator<T>, mapFn: (x: T) => U | undefined): U[] {
|
||||
const result: U[] = [];
|
||||
while (true) {
|
||||
const { value, done } = iter.next();
|
||||
if (done) break;
|
||||
const res = mapFn(value);
|
||||
if (res !== undefined) {
|
||||
result.push(res);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the first matching span of elements and returns a tuple of the first span
|
||||
* and the remaining elements.
|
||||
|
||||
@ -74,7 +74,7 @@ namespace ts {
|
||||
const projectService = new server.ProjectService(svcOpts);
|
||||
const rootScriptInfo = projectService.getOrCreateScriptInfo(rootFile, /* openedByClient */ true, /*containingProject*/ undefined);
|
||||
|
||||
const project = projectService.createInferredProjectWithRootFileIfNecessary(rootScriptInfo);
|
||||
const project = projectService.assignScriptInfoToInferredProject(rootScriptInfo);
|
||||
project.setCompilerOptions({ module: ts.ModuleKind.AMD, noLib: true } );
|
||||
return {
|
||||
project,
|
||||
|
||||
@ -263,6 +263,8 @@ namespace ts.projectSystem {
|
||||
|
||||
function invokeWatcherCallbacks<T>(callbacks: T[], invokeCallback: (cb: T) => void): void {
|
||||
if (callbacks) {
|
||||
// The array copy is made to ensure that even if one of the callback removes the callbacks,
|
||||
// we dont miss any callbacks following it
|
||||
const cbs = callbacks.slice();
|
||||
for (const cb of cbs) {
|
||||
invokeCallback(cb);
|
||||
|
||||
@ -271,16 +271,16 @@ namespace ts.server {
|
||||
ReloadingFiles = "Reloading configured projects for files",
|
||||
ReloadingInferredRootFiles = "Reloading configured projects for only inferred root files",
|
||||
UpdatedCallback = "Updated the callback",
|
||||
TrackingFileAdded = "Tracking file added",
|
||||
TrackingFileRemoved = "Tracking file removed",
|
||||
InferredRootAdded = "Inferred Root file added",
|
||||
InferredRootRemoved = "Inferred Root file removed",
|
||||
OpenFilesImpactedByConfigFileAdd = "File added to open files impacted by this config file",
|
||||
OpenFilesImpactedByConfigFileRemove = "File removed from open files impacted by this config file",
|
||||
RootOfInferredProjectTrue = "Open file was set as Inferred root",
|
||||
RootOfInferredProjectFalse = "Open file was set as not inferred root",
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export type ServerDirectoryWatcherCallback = (path: NormalizedPath) => void;
|
||||
|
||||
interface ConfigFileExistence {
|
||||
interface ConfigFileExistenceInfo {
|
||||
/**
|
||||
* Cached value of existence of config file
|
||||
* It is true if there is configured project open for this file.
|
||||
@ -290,18 +290,19 @@ namespace ts.server {
|
||||
*/
|
||||
exists: boolean;
|
||||
/**
|
||||
* The value in the trackingOpenFilesMap is true if the open file is inferred project root
|
||||
* and hence tracking changes to this config file
|
||||
* (either config file is already present but doesnt include the open file in the project structure or config file doesnt exist)
|
||||
*
|
||||
* Otherwise its false
|
||||
* openFilesImpactedByConfigFiles is a map of open files that would be impacted by this config file
|
||||
* because these are the paths being looked up for their default configured project location
|
||||
* The value in the map is true if the open file is root of the inferred project
|
||||
* It is false when the open file that would still be impacted by existance of
|
||||
* this config file but it is not the root of inferred project
|
||||
*/
|
||||
trackingOpenFilesMap: Map<boolean>;
|
||||
openFilesImpactedByConfigFile: Map<boolean>;
|
||||
/**
|
||||
* The file watcher corresponding to this config file for the inferred project root
|
||||
* The watcher is present only when there is no open configured project for this config file
|
||||
* The file watcher watching the config file because there is open script info that is root of
|
||||
* inferred project and will be impacted by change in the status of the config file
|
||||
* The watcher is present only when there is no open configured project for the config file
|
||||
*/
|
||||
configFileWatcher?: FileWatcher;
|
||||
configFileWatcherForRootOfInferredProject?: FileWatcher;
|
||||
}
|
||||
|
||||
export interface ProjectServiceOptions {
|
||||
@ -351,6 +352,9 @@ namespace ts.server {
|
||||
|
||||
private compilerOptionsForInferredProjects: CompilerOptions;
|
||||
private compileOnSaveForInferredProjects: boolean;
|
||||
/**
|
||||
* Project size for configured or external projects
|
||||
*/
|
||||
private readonly projectToSizeMap: Map<number> = createMap<number>();
|
||||
/**
|
||||
* This is a map of config file paths existance that doesnt need query to disk
|
||||
@ -359,7 +363,7 @@ namespace ts.server {
|
||||
* - Or it is present if we have configured project open with config file at that location
|
||||
* In this case the exists property is always true
|
||||
*/
|
||||
private readonly mapOfConfigFilePresence = createMap<ConfigFileExistence>();
|
||||
private readonly mapOfConfigFileExistenceInfo = createMap<ConfigFileExistenceInfo>();
|
||||
private readonly throttledOperations: ThrottledOperations;
|
||||
|
||||
private readonly hostConfiguration: HostConfiguration;
|
||||
@ -427,14 +431,17 @@ namespace ts.server {
|
||||
return this.changedFiles;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
ensureInferredProjectsUpToDate_TestOnly() {
|
||||
this.ensureProjectStructuresUptoDate();
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
getCompilerOptionsForInferredProjects() {
|
||||
return this.compilerOptionsForInferredProjects;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
onUpdateLanguageServiceStateForProject(project: Project, languageServiceEnabled: boolean) {
|
||||
if (!this.eventHandler) {
|
||||
return;
|
||||
@ -479,14 +486,13 @@ namespace ts.server {
|
||||
const projectName = project.getProjectName();
|
||||
this.pendingProjectUpdates.set(projectName, project);
|
||||
this.throttledOperations.schedule(projectName, /*delay*/ 250, () => {
|
||||
const project = this.pendingProjectUpdates.get(projectName);
|
||||
if (project) {
|
||||
this.pendingProjectUpdates.delete(projectName);
|
||||
if (this.pendingProjectUpdates.delete(projectName)) {
|
||||
project.updateGraph();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
delayUpdateProjectGraphAndInferredProjectsRefresh(project: Project) {
|
||||
this.delayUpdateProjectGraph(project);
|
||||
this.delayInferredProjectsRefresh();
|
||||
@ -513,7 +519,7 @@ namespace ts.server {
|
||||
this.delayUpdateProjectGraphs(this.inferredProjects);
|
||||
}
|
||||
|
||||
findProject(projectName: string): Project {
|
||||
findProject(projectName: string): Project | undefined {
|
||||
if (projectName === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
@ -687,39 +693,46 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private onConfigChangedForConfiguredProject(project: ConfiguredProject, eventKind: FileWatcherEventKind) {
|
||||
const configFilePresenceInfo = this.mapOfConfigFilePresence.get(project.canonicalConfigFilePath);
|
||||
const configFileExistenceInfo = this.mapOfConfigFileExistenceInfo.get(project.canonicalConfigFilePath);
|
||||
if (eventKind === FileWatcherEventKind.Deleted) {
|
||||
// Update the cached status
|
||||
// No action needed on tracking open files since the existing config file anyways didnt affect the tracking file
|
||||
configFilePresenceInfo.exists = false;
|
||||
// We arent updating or removing the cached config file presence info as that will be taken care of by
|
||||
// setConfigFilePresenceByClosedConfigFile when the project is closed (depending on tracking open files)
|
||||
configFileExistenceInfo.exists = false;
|
||||
this.removeProject(project);
|
||||
|
||||
// Reload the configured projects for the open files in the map as they are affectected by this config file
|
||||
this.logConfigFileWatchUpdate(project.getConfigFilePath(), configFilePresenceInfo, ConfigFileWatcherStatus.ReloadingFiles);
|
||||
// Since the configured project was deleted, we want to reload projects for all the open files
|
||||
this.delayReloadConfiguredProjectForFiles(configFilePresenceInfo.trackingOpenFilesMap, /*ignoreIfNotInferredProjectRoot*/ false);
|
||||
// Since the configured project was deleted, we want to reload projects for all the open files including files
|
||||
// that are not root of the inferred project
|
||||
this.logConfigFileWatchUpdate(project.getConfigFilePath(), project.canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.ReloadingFiles);
|
||||
this.delayReloadConfiguredProjectForFiles(configFileExistenceInfo, /*ignoreIfNotInferredProjectRoot*/ false);
|
||||
}
|
||||
else {
|
||||
this.logConfigFileWatchUpdate(project.getConfigFilePath(), configFilePresenceInfo, ConfigFileWatcherStatus.ReloadingInferredRootFiles);
|
||||
this.logConfigFileWatchUpdate(project.getConfigFilePath(), project.canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.ReloadingInferredRootFiles);
|
||||
project.pendingReload = true;
|
||||
this.delayUpdateProjectGraph(project);
|
||||
// As we scheduled the updated project graph, we would need to only schedule the project reload for the inferred project roots
|
||||
this.delayReloadConfiguredProjectForFiles(configFilePresenceInfo.trackingOpenFilesMap, /*ignoreIfNotInferredProjectRoot*/ true);
|
||||
// As we scheduled the update on configured project graph,
|
||||
// we would need to schedule the project reload for only the root of inferred projects
|
||||
this.delayReloadConfiguredProjectForFiles(configFileExistenceInfo, /*ignoreIfNotInferredProjectRoot*/ true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the callback function for the config file add/remove/change at any location that matters to open
|
||||
* script info but doesnt have configured project open for the config file
|
||||
* This is the callback function for the config file add/remove/change at any location
|
||||
* that matters to open script info but doesnt have configured project open
|
||||
* for the config file
|
||||
*/
|
||||
private onConfigFileChangeForOpenScriptInfo(configFileName: NormalizedPath, eventKind: FileWatcherEventKind) {
|
||||
// This callback is called only if we dont have config file project for this config file
|
||||
const cononicalConfigPath = normalizedPathToPath(configFileName, this.currentDirectory, this.toCanonicalFileName);
|
||||
const configFilePresenceInfo = this.mapOfConfigFilePresence.get(cononicalConfigPath);
|
||||
configFilePresenceInfo.exists = (eventKind !== FileWatcherEventKind.Deleted);
|
||||
this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.ReloadingFiles);
|
||||
// The tracking opens files would only contaion the inferred root so no need to check
|
||||
this.delayReloadConfiguredProjectForFiles(configFilePresenceInfo.trackingOpenFilesMap, /*ignoreIfNotInferredProjectRoot*/ false);
|
||||
const canonicalConfigPath = normalizedPathToPath(configFileName, this.currentDirectory, this.toCanonicalFileName);
|
||||
const configFileExistenceInfo = this.mapOfConfigFileExistenceInfo.get(canonicalConfigPath);
|
||||
configFileExistenceInfo.exists = (eventKind !== FileWatcherEventKind.Deleted);
|
||||
this.logConfigFileWatchUpdate(configFileName, canonicalConfigPath, configFileExistenceInfo, ConfigFileWatcherStatus.ReloadingFiles);
|
||||
|
||||
// Because there is no configured project open for the config file, the tracking open files map
|
||||
// will only have open files that need the re-detection of the project and hence
|
||||
// reload projects for all the tracking open files in the map
|
||||
this.delayReloadConfiguredProjectForFiles(configFileExistenceInfo, /*ignoreIfNotInferredProjectRoot*/ false);
|
||||
}
|
||||
|
||||
private removeProject(project: Project) {
|
||||
@ -737,7 +750,7 @@ namespace ts.server {
|
||||
case ProjectKind.Configured:
|
||||
this.configuredProjects.delete((<ConfiguredProject>project).canonicalConfigFilePath);
|
||||
this.projectToSizeMap.delete((project as ConfiguredProject).canonicalConfigFilePath);
|
||||
this.setConfigFilePresenceByClosedConfigFile(<ConfiguredProject>project);
|
||||
this.setConfigFileExistenceInfoByClosedConfiguredProject(<ConfiguredProject>project);
|
||||
break;
|
||||
case ProjectKind.Inferred:
|
||||
unorderedRemoveItem(this.inferredProjects, <InferredProject>project);
|
||||
@ -745,47 +758,16 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
private assignScriptInfoToInferredProjectIfNecessary(info: ScriptInfo, addToListOfOpenFiles: boolean): void {
|
||||
if (info.containingProjects.length === 0) {
|
||||
// create new inferred project p with the newly opened file as root
|
||||
// or add root to existing inferred project if 'useOneInferredProject' is true
|
||||
this.createInferredProjectWithRootFileIfNecessary(info);
|
||||
|
||||
// if useOneInferredProject is not set then try to fixup ownership of open files
|
||||
// check 'defaultProject !== inferredProject' is necessary to handle cases
|
||||
// when creation inferred project for some file has added other open files into this project
|
||||
// (i.e.as referenced files)
|
||||
// we definitely don't want to delete the project that was just created
|
||||
// Also note that we need to create a copy of the array since the list of project will change
|
||||
for (const inferredProject of this.inferredProjects.slice(0, this.inferredProjects.length - 1)) {
|
||||
Debug.assert(!this.useSingleInferredProject);
|
||||
// Remove this file from the root of inferred project if its part of more than 2 projects
|
||||
// This logic is same as iterating over all open files and calling
|
||||
// this.removRootOfInferredProjectIfNowPartOfOtherProject(f);
|
||||
// Since this is also called from refreshInferredProject and closeOpen file
|
||||
// to update inferred projects of the open file, this iteration might be faster
|
||||
// instead of scanning all open files
|
||||
const root = inferredProject.getRootScriptInfos();
|
||||
Debug.assert(root.length === 1);
|
||||
if (root[0].containingProjects.length > 1) {
|
||||
this.removeProject(inferredProject);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (const p of info.containingProjects) {
|
||||
// file is the part of configured project
|
||||
if (p.projectKind === ProjectKind.Configured) {
|
||||
if (addToListOfOpenFiles) {
|
||||
((<ConfiguredProject>p)).addOpenRef();
|
||||
}
|
||||
}
|
||||
private addToListOfOpenFiles(info: ScriptInfo) {
|
||||
Debug.assert(info.containingProjects.length !== 0);
|
||||
for (const p of info.containingProjects) {
|
||||
// file is the part of configured project, addref the project
|
||||
if (p.projectKind === ProjectKind.Configured) {
|
||||
((<ConfiguredProject>p)).addOpenRef();
|
||||
}
|
||||
}
|
||||
|
||||
if (addToListOfOpenFiles) {
|
||||
this.openFiles.push(info);
|
||||
}
|
||||
this.openFiles.push(info);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -837,17 +819,16 @@ namespace ts.server {
|
||||
this.removeProject(project);
|
||||
}
|
||||
|
||||
// collect orphaned files and try to re-add them as newly opened
|
||||
// treat orphaned files as newly opened
|
||||
// for all open files
|
||||
// collect orphaned files and assign them to inferred project just like we treat open of a file
|
||||
for (const f of this.openFiles) {
|
||||
if (f.containingProjects.length === 0) {
|
||||
this.assignScriptInfoToInferredProjectIfNecessary(f, /*addToListOfOpenFiles*/ false);
|
||||
this.assignScriptInfoToInferredProject(f);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup script infos that arent part of any project is postponed to
|
||||
// next file open so that if file from same project is opened we wont end up creating same script infos
|
||||
// Cleanup script infos that arent part of any project (eg. those could be closed script infos not referenced by any project)
|
||||
// is postponed to next file open so that if file from same project is opened,
|
||||
// we wont end up creating same script infos
|
||||
}
|
||||
|
||||
// If the current info is being just closed - add the watcher file to track changes
|
||||
@ -871,15 +852,15 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private configFileExists(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo) {
|
||||
let configFilePresenceInfo = this.mapOfConfigFilePresence.get(canonicalConfigFilePath);
|
||||
if (configFilePresenceInfo) {
|
||||
// By default the info is belong to the config file.
|
||||
// Only adding the info as a root to inferred project will make it the root
|
||||
if (!configFilePresenceInfo.trackingOpenFilesMap.has(info.path)) {
|
||||
configFilePresenceInfo.trackingOpenFilesMap.set(info.path, false);
|
||||
this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileAdded);
|
||||
let configFileExistenceInfo = this.mapOfConfigFileExistenceInfo.get(canonicalConfigFilePath);
|
||||
if (configFileExistenceInfo) {
|
||||
// By default the info would get impacted by presence of config file since its in the detection path
|
||||
// Only adding the info as a root to inferred project will need the existence to be watched by file watcher
|
||||
if (!configFileExistenceInfo.openFilesImpactedByConfigFile.has(info.path)) {
|
||||
configFileExistenceInfo.openFilesImpactedByConfigFile.set(info.path, false);
|
||||
this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.OpenFilesImpactedByConfigFileAdd);
|
||||
}
|
||||
return configFilePresenceInfo.exists;
|
||||
return configFileExistenceInfo.exists;
|
||||
}
|
||||
|
||||
// Theoretically we should be adding watch for the directory here itself.
|
||||
@ -887,162 +868,148 @@ namespace ts.server {
|
||||
// somewhere inside the another config file directory.
|
||||
// And technically we could handle that case in configFile's directory watcher in some cases
|
||||
// But given that its a rare scenario it seems like too much overhead. (we werent watching those directories earlier either)
|
||||
// So what we are now watching is: configFile if the project is open
|
||||
// And the whole chain of config files only for the inferred project roots
|
||||
|
||||
// Cache the host value of file exists and add the info tio to the tracked root
|
||||
const trackingOpenFilesMap = createMap<boolean>();
|
||||
trackingOpenFilesMap.set(info.path, false);
|
||||
// So what we are now watching is: configFile if the configured project corresponding to it is open
|
||||
// Or the whole chain of config files for the roots of the inferred projects
|
||||
|
||||
// Cache the host value of file exists and add the info to map of open files impacted by this config file
|
||||
const openFilesImpactedByConfigFile = createMap<boolean>();
|
||||
openFilesImpactedByConfigFile.set(info.path, false);
|
||||
const exists = this.host.fileExists(configFileName);
|
||||
configFilePresenceInfo = { exists, trackingOpenFilesMap };
|
||||
this.mapOfConfigFilePresence.set(canonicalConfigFilePath, configFilePresenceInfo);
|
||||
this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileAdded);
|
||||
configFileExistenceInfo = { exists, openFilesImpactedByConfigFile };
|
||||
this.mapOfConfigFileExistenceInfo.set(canonicalConfigFilePath, configFileExistenceInfo);
|
||||
this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.OpenFilesImpactedByConfigFileAdd);
|
||||
return exists;
|
||||
}
|
||||
|
||||
private setConfigFilePresenceByNewConfiguredProject(project: ConfiguredProject) {
|
||||
const configFilePresenceInfo = this.mapOfConfigFilePresence.get(project.canonicalConfigFilePath);
|
||||
if (configFilePresenceInfo) {
|
||||
Debug.assert(configFilePresenceInfo.exists);
|
||||
private setConfigFileExistenceByNewConfiguredProject(project: ConfiguredProject) {
|
||||
const configFileExistenceInfo = this.mapOfConfigFileExistenceInfo.get(project.canonicalConfigFilePath);
|
||||
if (configFileExistenceInfo) {
|
||||
Debug.assert(configFileExistenceInfo.exists);
|
||||
// close existing watcher
|
||||
if (configFilePresenceInfo.configFileWatcher) {
|
||||
if (configFileExistenceInfo.configFileWatcherForRootOfInferredProject) {
|
||||
const configFileName = project.getConfigFilePath();
|
||||
this.closeFileWatcher(
|
||||
WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName,
|
||||
configFilePresenceInfo.configFileWatcher, WatcherCloseReason.ConfigProjectCreated
|
||||
configFileExistenceInfo.configFileWatcherForRootOfInferredProject, WatcherCloseReason.ConfigProjectCreated
|
||||
);
|
||||
configFilePresenceInfo.configFileWatcher = undefined;
|
||||
this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback);
|
||||
configFileExistenceInfo.configFileWatcherForRootOfInferredProject = undefined;
|
||||
this.logConfigFileWatchUpdate(configFileName, project.canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.UpdatedCallback);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// We could be in this scenario if it is the external project tracked configured file
|
||||
// We could be in this scenario if project is the configured project tracked by external project
|
||||
// Since that route doesnt check if the config file is present or not
|
||||
this.mapOfConfigFilePresence.set(project.canonicalConfigFilePath, {
|
||||
this.mapOfConfigFileExistenceInfo.set(project.canonicalConfigFilePath, {
|
||||
exists: true,
|
||||
trackingOpenFilesMap: createMap<boolean>()
|
||||
openFilesImpactedByConfigFile: createMap<boolean>()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private configFileExistenceTracksInferredRoot(configFilePresenceInfo: ConfigFileExistence) {
|
||||
return forEachEntry(configFilePresenceInfo.trackingOpenFilesMap, (value, __key) => value);
|
||||
/**
|
||||
* Returns true if the configFileExistenceInfo is needed/impacted by open files that are root of inferred project
|
||||
*/
|
||||
private configFileExistenceImpactsRootOfInferredProject(configFileExistenceInfo: ConfigFileExistenceInfo) {
|
||||
return forEachEntry(configFileExistenceInfo.openFilesImpactedByConfigFile, (isRootOfInferredProject, __key) => isRootOfInferredProject);
|
||||
}
|
||||
|
||||
private setConfigFilePresenceByClosedConfigFile(closedProject: ConfiguredProject) {
|
||||
const configFilePresenceInfo = this.mapOfConfigFilePresence.get(closedProject.canonicalConfigFilePath);
|
||||
Debug.assert(!!configFilePresenceInfo);
|
||||
const trackingOpenFilesMap = configFilePresenceInfo.trackingOpenFilesMap;
|
||||
if (trackingOpenFilesMap.size) {
|
||||
private setConfigFileExistenceInfoByClosedConfiguredProject(closedProject: ConfiguredProject) {
|
||||
const configFileExistenceInfo = this.mapOfConfigFileExistenceInfo.get(closedProject.canonicalConfigFilePath);
|
||||
Debug.assert(!!configFileExistenceInfo);
|
||||
if (configFileExistenceInfo.openFilesImpactedByConfigFile.size) {
|
||||
const configFileName = closedProject.getConfigFilePath();
|
||||
if (this.configFileExistenceTracksInferredRoot(configFilePresenceInfo)) {
|
||||
Debug.assert(!configFilePresenceInfo.configFileWatcher);
|
||||
configFilePresenceInfo.configFileWatcher = this.addFileWatcher(
|
||||
// If there are open files that are impacted by this config file existence
|
||||
// but none of them are root of inferred project, the config file watcher will be
|
||||
// created when any of the script infos are added as root of inferred project
|
||||
if (this.configFileExistenceImpactsRootOfInferredProject(configFileExistenceInfo)) {
|
||||
Debug.assert(!configFileExistenceInfo.configFileWatcherForRootOfInferredProject);
|
||||
configFileExistenceInfo.configFileWatcherForRootOfInferredProject = this.addFileWatcher(
|
||||
WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName,
|
||||
(_filename, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind)
|
||||
);
|
||||
this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback);
|
||||
this.logConfigFileWatchUpdate(configFileName, closedProject.canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.UpdatedCallback);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// There is no one tracking anymore. Remove the status
|
||||
this.mapOfConfigFilePresence.delete(closedProject.canonicalConfigFilePath);
|
||||
// There is not a single file open thats tracking the status of this config file. Remove from cache
|
||||
this.mapOfConfigFileExistenceInfo.delete(closedProject.canonicalConfigFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
private logConfigFileWatchUpdate(configFileName: NormalizedPath, configFilePresenceInfo: ConfigFileExistence, status: ConfigFileWatcherStatus) {
|
||||
private logConfigFileWatchUpdate(configFileName: NormalizedPath, canonicalConfigFilePath: string, configFileExistenceInfo: ConfigFileExistenceInfo, status: ConfigFileWatcherStatus) {
|
||||
if (!this.logger.loggingEnabled()) {
|
||||
return;
|
||||
}
|
||||
const inferredRoots: string[] = [];
|
||||
const otherFiles: string[] = [];
|
||||
configFilePresenceInfo.trackingOpenFilesMap.forEach((value, key: Path) => {
|
||||
const info = this.getScriptInfoForPath(key);
|
||||
if (value) {
|
||||
inferredRoots.push(info.fileName);
|
||||
}
|
||||
else {
|
||||
otherFiles.push(info.fileName);
|
||||
}
|
||||
configFileExistenceInfo.openFilesImpactedByConfigFile.forEach((isRootOfInferredProject, key) => {
|
||||
const info = this.getScriptInfoForPath(key as Path);
|
||||
(isRootOfInferredProject ? inferredRoots : otherFiles).push(info.fileName);
|
||||
});
|
||||
const watchType = status === ConfigFileWatcherStatus.UpdatedCallback ||
|
||||
status === ConfigFileWatcherStatus.ReloadingFiles ||
|
||||
status === ConfigFileWatcherStatus.ReloadingInferredRootFiles ?
|
||||
(configFilePresenceInfo.configFileWatcher ? WatchType.ConfigFileForInferredRoot : WatchType.ConfigFilePath) :
|
||||
"";
|
||||
this.logger.info(`ConfigFilePresence ${watchType}:: File: ${configFileName} Currently Tracking: InferredRootFiles: ${inferredRoots} OtherFiles: ${otherFiles} Status: ${status}`);
|
||||
|
||||
const watches: WatchType[] = [];
|
||||
if (configFileExistenceInfo.configFileWatcherForRootOfInferredProject) {
|
||||
watches.push(WatchType.ConfigFileForInferredRoot);
|
||||
}
|
||||
if (this.configuredProjects.has(canonicalConfigFilePath)) {
|
||||
watches.push(WatchType.ConfigFilePath);
|
||||
}
|
||||
this.logger.info(`ConfigFilePresence:: Current Watches: ['${watches.join("','")}']:: File: ${configFileName} Currently impacted open files: RootsOfInferredProjects: ${inferredRoots} OtherOpenFiles: ${otherFiles} Status: ${status}`);
|
||||
}
|
||||
|
||||
private closeConfigFileWatcherIfInferredRoot(configFileName: NormalizedPath, canonicalConfigFilePath: string,
|
||||
configFilePresenceInfo: ConfigFileExistence, infoIsInferredRoot: boolean, reason: WatcherCloseReason) {
|
||||
// Close the config file watcher if it was the last inferred root
|
||||
if (infoIsInferredRoot &&
|
||||
configFilePresenceInfo.configFileWatcher &&
|
||||
!this.configFileExistenceTracksInferredRoot(configFilePresenceInfo)) {
|
||||
/**
|
||||
* Close the config file watcher in the cached ConfigFileExistenceInfo
|
||||
* if there arent any open files that are root of inferred project
|
||||
*/
|
||||
private closeConfigFileWatcherOfConfigFileExistenceInfo(
|
||||
configFileName: NormalizedPath, configFileExistenceInfo: ConfigFileExistenceInfo,
|
||||
reason: WatcherCloseReason
|
||||
) {
|
||||
// Close the config file watcher if there are no more open files that are root of inferred project
|
||||
if (configFileExistenceInfo.configFileWatcherForRootOfInferredProject &&
|
||||
!this.configFileExistenceImpactsRootOfInferredProject(configFileExistenceInfo)) {
|
||||
this.closeFileWatcher(
|
||||
WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName,
|
||||
configFilePresenceInfo.configFileWatcher, reason
|
||||
);
|
||||
configFilePresenceInfo.configFileWatcher = undefined;
|
||||
}
|
||||
|
||||
// If this was the last tracking file open for this config file, remove the cached value
|
||||
if (!configFilePresenceInfo.trackingOpenFilesMap.size &&
|
||||
!this.getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath)) {
|
||||
this.mapOfConfigFilePresence.delete(canonicalConfigFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
private closeConfigFileWatchForClosedScriptInfo(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo) {
|
||||
const configFilePresenceInfo = this.mapOfConfigFilePresence.get(canonicalConfigFilePath);
|
||||
if (configFilePresenceInfo) {
|
||||
const isInferredRoot = configFilePresenceInfo.trackingOpenFilesMap.get(info.path);
|
||||
|
||||
// Delete the info from tracking
|
||||
configFilePresenceInfo.trackingOpenFilesMap.delete(info.path);
|
||||
this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileRemoved);
|
||||
|
||||
// Close the config file watcher if it was the last inferred root
|
||||
this.closeConfigFileWatcherIfInferredRoot(configFileName, canonicalConfigFilePath,
|
||||
configFilePresenceInfo, isInferredRoot, WatcherCloseReason.FileClosed
|
||||
configFileExistenceInfo.configFileWatcherForRootOfInferredProject, reason
|
||||
);
|
||||
configFileExistenceInfo.configFileWatcherForRootOfInferredProject = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called on file close, so that we stop watching the config file for this script info
|
||||
* @param info
|
||||
*/
|
||||
private stopWatchingConfigFilesForClosedScriptInfo(info: ScriptInfo) {
|
||||
Debug.assert(!info.isScriptOpen());
|
||||
this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) =>
|
||||
this.closeConfigFileWatchForClosedScriptInfo(configFileName, canonicalConfigFilePath, info)
|
||||
);
|
||||
}
|
||||
this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => {
|
||||
const configFileExistenceInfo = this.mapOfConfigFileExistenceInfo.get(canonicalConfigFilePath);
|
||||
if (configFileExistenceInfo) {
|
||||
const infoIsRootOfInferredProject = configFileExistenceInfo.openFilesImpactedByConfigFile.get(info.path);
|
||||
|
||||
private watchConfigFileForInferredProjectRoot(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo) {
|
||||
let configFilePresenceInfo = this.mapOfConfigFilePresence.get(canonicalConfigFilePath);
|
||||
if (!configFilePresenceInfo) {
|
||||
// Create the cache
|
||||
configFilePresenceInfo = {
|
||||
exists: this.host.fileExists(configFileName),
|
||||
trackingOpenFilesMap: createMap<boolean>()
|
||||
};
|
||||
this.mapOfConfigFilePresence.set(canonicalConfigFilePath, configFilePresenceInfo);
|
||||
}
|
||||
// Delete the info from map, since this file is no more open
|
||||
configFileExistenceInfo.openFilesImpactedByConfigFile.delete(info.path);
|
||||
this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.OpenFilesImpactedByConfigFileRemove);
|
||||
|
||||
// Set this file as inferred root
|
||||
configFilePresenceInfo.trackingOpenFilesMap.set(info.path, true);
|
||||
this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.InferredRootAdded);
|
||||
// If the script info was not root of inferred project,
|
||||
// there wont be config file watch open because of this script info
|
||||
if (infoIsRootOfInferredProject) {
|
||||
// But if it is a root, it could be the last script info that is root of inferred project
|
||||
// and hence we would need to close the config file watcher
|
||||
this.closeConfigFileWatcherOfConfigFileExistenceInfo(
|
||||
configFileName, configFileExistenceInfo, WatcherCloseReason.FileClosed
|
||||
);
|
||||
}
|
||||
|
||||
// If there is no configured project for this config file, create the watcher
|
||||
if (!configFilePresenceInfo.configFileWatcher &&
|
||||
!this.getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath)) {
|
||||
configFilePresenceInfo.configFileWatcher = this.addFileWatcher(WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName,
|
||||
(_fileName, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind)
|
||||
);
|
||||
this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback);
|
||||
}
|
||||
// If there are no open files that are impacted by configFileExistenceInfo after closing this script info
|
||||
// there is no configured project present, remove the cached existence info
|
||||
if (!configFileExistenceInfo.openFilesImpactedByConfigFile.size &&
|
||||
!this.getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath)) {
|
||||
Debug.assert(!configFileExistenceInfo.configFileWatcherForRootOfInferredProject);
|
||||
this.mapOfConfigFileExistenceInfo.delete(canonicalConfigFilePath);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1051,25 +1018,30 @@ namespace ts.server {
|
||||
/* @internal */
|
||||
startWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo) {
|
||||
Debug.assert(info.isScriptOpen());
|
||||
this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) =>
|
||||
this.watchConfigFileForInferredProjectRoot(configFileName, canonicalConfigFilePath, info)
|
||||
);
|
||||
}
|
||||
|
||||
private closeWatchConfigFileForInferredProjectRoot(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo, reason: WatcherCloseReason) {
|
||||
const configFilePresenceInfo = this.mapOfConfigFilePresence.get(canonicalConfigFilePath);
|
||||
if (configFilePresenceInfo) {
|
||||
// Set this as not inferred root
|
||||
if (configFilePresenceInfo.trackingOpenFilesMap.has(info.path)) {
|
||||
configFilePresenceInfo.trackingOpenFilesMap.set(info.path, false);
|
||||
this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.InferredRootRemoved);
|
||||
this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => {
|
||||
let configFilePresenceInfo = this.mapOfConfigFileExistenceInfo.get(canonicalConfigFilePath);
|
||||
if (!configFilePresenceInfo) {
|
||||
// Create the cache
|
||||
configFilePresenceInfo = {
|
||||
exists: this.host.fileExists(configFileName),
|
||||
openFilesImpactedByConfigFile: createMap<boolean>()
|
||||
};
|
||||
this.mapOfConfigFileExistenceInfo.set(canonicalConfigFilePath, configFilePresenceInfo);
|
||||
}
|
||||
|
||||
// Close the watcher if present
|
||||
this.closeConfigFileWatcherIfInferredRoot(configFileName, canonicalConfigFilePath,
|
||||
configFilePresenceInfo, /*infoIsInferredRoot*/ true, reason
|
||||
);
|
||||
}
|
||||
// Set this file as the root of inferred project
|
||||
configFilePresenceInfo.openFilesImpactedByConfigFile.set(info.path, true);
|
||||
this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFilePresenceInfo, ConfigFileWatcherStatus.RootOfInferredProjectTrue);
|
||||
|
||||
// If there is no configured project for this config file, add the file watcher
|
||||
if (!configFilePresenceInfo.configFileWatcherForRootOfInferredProject &&
|
||||
!this.getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath)) {
|
||||
configFilePresenceInfo.configFileWatcherForRootOfInferredProject = this.addFileWatcher(WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName,
|
||||
(_fileName, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind)
|
||||
);
|
||||
this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1077,9 +1049,21 @@ namespace ts.server {
|
||||
*/
|
||||
/* @internal */
|
||||
stopWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo, reason: WatcherCloseReason) {
|
||||
this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) =>
|
||||
this.closeWatchConfigFileForInferredProjectRoot(configFileName, canonicalConfigFilePath, info, reason)
|
||||
);
|
||||
this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => {
|
||||
const configFileExistenceInfo = this.mapOfConfigFileExistenceInfo.get(canonicalConfigFilePath);
|
||||
if (configFileExistenceInfo && configFileExistenceInfo.openFilesImpactedByConfigFile.has(info.path)) {
|
||||
Debug.assert(info.isScriptOpen());
|
||||
|
||||
// Info is not root of inferred project any more
|
||||
configFileExistenceInfo.openFilesImpactedByConfigFile.set(info.path, false);
|
||||
this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.RootOfInferredProjectFalse);
|
||||
|
||||
// Close the config file watcher
|
||||
this.closeConfigFileWatcherOfConfigFileExistenceInfo(
|
||||
configFileName, configFileExistenceInfo, reason
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1165,7 +1149,6 @@ namespace ts.server {
|
||||
|
||||
function printProjects(logger: Logger, projects: Project[], counter: number) {
|
||||
for (const project of projects) {
|
||||
// Print shouldnt update the graph. It should emit whatever state the project is currently in
|
||||
logger.info(`Project '${project.getProjectName()}' (${ProjectKind[project.projectKind]}) ${counter}`);
|
||||
logger.info(project.filesToString());
|
||||
logger.info("-----------------------------------------------");
|
||||
@ -1175,13 +1158,13 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
private findConfiguredProjectByProjectName(configFileName: NormalizedPath) {
|
||||
private findConfiguredProjectByProjectName(configFileName: NormalizedPath): ConfiguredProject | undefined {
|
||||
// make sure that casing of config file name is consistent
|
||||
const canonicalConfigFilePath = asNormalizedPath(this.toCanonicalFileName(configFileName));
|
||||
return this.getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath);
|
||||
}
|
||||
|
||||
private getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath: string) {
|
||||
private getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath: string): ConfiguredProject | undefined {
|
||||
return this.configuredProjects.get(canonicalConfigFilePath);
|
||||
}
|
||||
|
||||
@ -1259,7 +1242,7 @@ namespace ts.server {
|
||||
return false;
|
||||
}
|
||||
|
||||
private createAndAddExternalProject(projectFileName: string, files: protocol.ExternalFile[], options: protocol.ExternalProjectCompilerOptions, typeAcquisition: TypeAcquisition) {
|
||||
private createExternalProject(projectFileName: string, files: protocol.ExternalFile[], options: protocol.ExternalProjectCompilerOptions, typeAcquisition: TypeAcquisition) {
|
||||
const compilerOptions = convertCompilerOptions(options);
|
||||
const project = new ExternalProject(
|
||||
projectFileName,
|
||||
@ -1319,7 +1302,18 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
private createAndAddConfiguredProject(configFileName: NormalizedPath, projectOptions: ProjectOptions, configFileErrors: Diagnostic[], configFileSpecs: ConfigFileSpecs, cachedServerHost: CachedServerHost, clientFileName?: string) {
|
||||
private addFilesToNonInferredProjectAndUpdateGraph<T>(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader<T>, clientFileName: string, typeAcquisition: TypeAcquisition, configFileErrors: Diagnostic[]): void {
|
||||
project.setProjectErrors(configFileErrors);
|
||||
this.updateNonInferredProjectFiles(project, files, propertyReader, clientFileName);
|
||||
project.setTypeAcquisition(typeAcquisition);
|
||||
// This doesnt need scheduling since its either creation or reload of the project
|
||||
project.updateGraph();
|
||||
}
|
||||
|
||||
private createConfiguredProject(configFileName: NormalizedPath, clientFileName?: string) {
|
||||
const cachedServerHost = new CachedServerHost(this.host, this.toCanonicalFileName);
|
||||
const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedServerHost);
|
||||
this.logger.info(`Opened configuration file ${configFileName}`);
|
||||
const languageServiceEnabled = !this.exceededTotalSizeLimitForNonTsFiles(configFileName, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader);
|
||||
const project = new ConfiguredProject(
|
||||
configFileName,
|
||||
@ -1332,7 +1326,7 @@ namespace ts.server {
|
||||
cachedServerHost);
|
||||
|
||||
project.configFileSpecs = configFileSpecs;
|
||||
// TODO: (sheetalkamat) We should also watch the configFiles that are extended
|
||||
// TODO: We probably should also watch the configFiles that are extended
|
||||
project.configFileWatcher = this.addFileWatcher(WatchType.ConfigFilePath, project,
|
||||
configFileName, (_fileName, eventKind) => this.onConfigChangedForConfiguredProject(project, eventKind)
|
||||
);
|
||||
@ -1343,26 +1337,11 @@ namespace ts.server {
|
||||
|
||||
this.addFilesToNonInferredProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typeAcquisition, configFileErrors);
|
||||
this.configuredProjects.set(project.canonicalConfigFilePath, project);
|
||||
this.setConfigFilePresenceByNewConfiguredProject(project);
|
||||
this.setConfigFileExistenceByNewConfiguredProject(project);
|
||||
this.sendProjectTelemetry(project.getConfigFilePath(), project, projectOptions);
|
||||
return project;
|
||||
}
|
||||
|
||||
private addFilesToNonInferredProjectAndUpdateGraph<T>(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader<T>, clientFileName: string, typeAcquisition: TypeAcquisition, configFileErrors: Diagnostic[]): void {
|
||||
project.setProjectErrors(configFileErrors);
|
||||
this.updateNonInferredProjectFiles(project, files, propertyReader, clientFileName);
|
||||
project.setTypeAcquisition(typeAcquisition);
|
||||
// This doesnt need scheduling since its either creation or reload of the project
|
||||
project.updateGraph();
|
||||
}
|
||||
|
||||
private openConfigFile(configFileName: NormalizedPath, clientFileName?: string) {
|
||||
const cachedServerHost = new CachedServerHost(this.host, this.toCanonicalFileName);
|
||||
const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedServerHost);
|
||||
this.logger.info(`Opened configuration file ${configFileName}`);
|
||||
return this.createAndAddConfiguredProject(configFileName, projectOptions, configFileErrors, configFileSpecs, cachedServerHost, clientFileName);
|
||||
}
|
||||
|
||||
private updateNonInferredProjectFiles<T>(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader<T>, clientFileName?: string) {
|
||||
const projectRootFilesMap = project.getRootFilesMap();
|
||||
const newRootScriptInfoMap: Map<ProjectRoot> = createMap<ProjectRoot>();
|
||||
@ -1474,18 +1453,44 @@ namespace ts.server {
|
||||
this.updateNonInferredProject(project, projectOptions.files, fileNamePropertyReader, projectOptions.compilerOptions, projectOptions.typeAcquisition, projectOptions.compileOnSave, configFileErrors);
|
||||
}
|
||||
|
||||
createInferredProjectWithRootFileIfNecessary(root: ScriptInfo) {
|
||||
/*@internal*/
|
||||
assignScriptInfoToInferredProject(info: ScriptInfo) {
|
||||
Debug.assert(info.containingProjects.length === 0);
|
||||
|
||||
// create new inferred project p with the newly opened file as root
|
||||
// or add root to existing inferred project if 'useSingleInferredProject' is true
|
||||
const useExistingProject = this.useSingleInferredProject && this.inferredProjects.length;
|
||||
const project = useExistingProject
|
||||
? this.inferredProjects[0]
|
||||
: new InferredProject(this, this.documentRegistry, this.compilerOptionsForInferredProjects);
|
||||
|
||||
project.addRoot(root);
|
||||
project.addRoot(info);
|
||||
project.updateGraph();
|
||||
|
||||
if (!useExistingProject) {
|
||||
// Add the new project to inferred projects list
|
||||
this.inferredProjects.push(project);
|
||||
|
||||
for (const inferredProject of this.inferredProjects.slice(0, this.inferredProjects.length - 1)) {
|
||||
// Note that we need to create a copy of the array since the list of project can change
|
||||
Debug.assert(inferredProject !== project);
|
||||
|
||||
// Remove the inferred project if the root of it is now part of newly created inferred project
|
||||
// e.g through references
|
||||
// Which means if any root of inferred project is part of more than 1 project can be removed
|
||||
// This logic is same as iterating over all open files and calling
|
||||
// this.removeRootOfInferredProjectIfNowPartOfOtherProject(f);
|
||||
// Since this is also called from refreshInferredProject and closeOpen file
|
||||
// to update inferred projects of the open file, this iteration might be faster
|
||||
// instead of scanning all open files
|
||||
const root = inferredProject.getRootScriptInfos();
|
||||
Debug.assert(root.length === 1);
|
||||
if (root[0].containingProjects.length > 1) {
|
||||
this.removeProject(inferredProject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return project;
|
||||
}
|
||||
|
||||
@ -1632,28 +1637,33 @@ namespace ts.server {
|
||||
*/
|
||||
reloadProjects() {
|
||||
this.logger.info("reload projects.");
|
||||
this.reloadConfiguredProjectForFiles(this.openFiles, /*delayReload*/ false);
|
||||
this.reloadConfiguredsProjectForFiles(this.openFiles, /*delayReload*/ false);
|
||||
this.refreshInferredProjects();
|
||||
}
|
||||
|
||||
delayReloadConfiguredProjectForFiles(openFilesMap: Map<boolean>, ignoreIfNotInferredProjectRoot: boolean) {
|
||||
private delayReloadConfiguredProjectForFiles(configFileExistenceInfo: ConfigFileExistenceInfo, ignoreIfNotRootOfInferredProject: boolean) {
|
||||
// Get open files to reload projects for
|
||||
const openFiles = flatMapIter(openFilesMap.keys(), path => {
|
||||
if (!ignoreIfNotInferredProjectRoot || openFilesMap.get(path)) {
|
||||
return this.getScriptInfoForPath(path as Path);
|
||||
const openFiles = mapDefinedIter(
|
||||
configFileExistenceInfo.openFilesImpactedByConfigFile.entries(),
|
||||
([path, isRootOfInferredProject]) => {
|
||||
if (!ignoreIfNotRootOfInferredProject || isRootOfInferredProject) {
|
||||
const info = this.getScriptInfoForPath(path as Path);
|
||||
Debug.assert(!!info);
|
||||
return info;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.reloadConfiguredProjectForFiles(openFiles, /*delayReload*/ true);
|
||||
);
|
||||
this.reloadConfiguredsProjectForFiles(openFiles, /*delayReload*/ true);
|
||||
this.delayInferredProjectsRefresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* This function goes through all the openFiles and tries to file the config file for them.
|
||||
* If the config file is found and it refers to existing project, it reloads it either immediately
|
||||
* or schedules it for reload depending on delayedReload option
|
||||
* or schedules it for reload depending on delayReload option
|
||||
* If the there is no existing project it just opens the configured project for the config file
|
||||
*/
|
||||
reloadConfiguredProjectForFiles(openFiles: ScriptInfo[], delayReload: boolean) {
|
||||
private reloadConfiguredsProjectForFiles(openFiles: ScriptInfo[], delayReload: boolean) {
|
||||
const mapUpdatedProjects = createMap<true>();
|
||||
// try to reload config file for all open files
|
||||
for (const info of openFiles) {
|
||||
@ -1665,7 +1675,7 @@ namespace ts.server {
|
||||
if (configFileName) {
|
||||
let project = this.findConfiguredProjectByProjectName(configFileName);
|
||||
if (!project) {
|
||||
project = this.openConfigFile(configFileName, info.fileName);
|
||||
project = this.createConfiguredProject(configFileName, info.fileName);
|
||||
mapUpdatedProjects.set(configFileName, true);
|
||||
}
|
||||
else if (!mapUpdatedProjects.has(configFileName)) {
|
||||
@ -1716,7 +1726,7 @@ namespace ts.server {
|
||||
for (const info of this.openFiles) {
|
||||
// collect all orphaned script infos from open files
|
||||
if (info.containingProjects.length === 0) {
|
||||
this.assignScriptInfoToInferredProjectIfNecessary(info, /*addToListOfOpenFiles*/ false);
|
||||
this.assignScriptInfoToInferredProject(info);
|
||||
}
|
||||
// Or remove the root of inferred project if is referenced in more than one projects
|
||||
else {
|
||||
@ -1752,7 +1762,7 @@ namespace ts.server {
|
||||
if (configFileName) {
|
||||
project = this.findConfiguredProjectByProjectName(configFileName);
|
||||
if (!project) {
|
||||
project = this.openConfigFile(configFileName, fileName);
|
||||
project = this.createConfiguredProject(configFileName, fileName);
|
||||
|
||||
// even if opening config file was successful, it could still
|
||||
// contain errors that were tolerated.
|
||||
@ -1771,8 +1781,13 @@ namespace ts.server {
|
||||
project.markAsDirty();
|
||||
}
|
||||
|
||||
// at this point if file is the part of some configured/external project then this project should be created
|
||||
this.assignScriptInfoToInferredProjectIfNecessary(info, /*addToListOfOpenFiles*/ true);
|
||||
// At this point if file is part of any any configured or external project, then it would be present in the containing projects
|
||||
// So if it still doesnt have any containing projects, it needs to be part of inferred project
|
||||
if (info.containingProjects.length === 0) {
|
||||
this.assignScriptInfoToInferredProject(info);
|
||||
}
|
||||
this.addToListOfOpenFiles(info);
|
||||
|
||||
// Delete the orphan files here because there might be orphan script infos (which are not part of project)
|
||||
// when some file/s were closed which resulted in project removal.
|
||||
// It was then postponed to cleanup these script infos so that they can be reused if
|
||||
@ -2084,7 +2099,7 @@ namespace ts.server {
|
||||
let project = this.findConfiguredProjectByProjectName(tsconfigFile);
|
||||
if (!project) {
|
||||
// errors are stored in the project
|
||||
project = this.openConfigFile(tsconfigFile);
|
||||
project = this.createConfiguredProject(tsconfigFile);
|
||||
}
|
||||
if (project && !contains(exisingConfigFiles, tsconfigFile)) {
|
||||
// keep project alive even if no documents are opened - its lifetime is bound to the lifetime of containing external project
|
||||
@ -2095,7 +2110,7 @@ namespace ts.server {
|
||||
else {
|
||||
// no config files - remove the item from the collection
|
||||
this.externalProjectToConfiguredProjectMap.delete(proj.projectFileName);
|
||||
this.createAndAddExternalProject(proj.projectFileName, rootFiles, proj.options, proj.typeAcquisition);
|
||||
this.createExternalProject(proj.projectFileName, rootFiles, proj.options, proj.typeAcquisition);
|
||||
}
|
||||
if (!suppressRefreshOfInferredProjects) {
|
||||
this.ensureProjectStructuresUptoDate(/*refreshInferredProjects*/ true);
|
||||
|
||||
@ -35,7 +35,7 @@ namespace ts {
|
||||
|
||||
export function getApplicableRefactors(context: RefactorContext): ApplicableRefactorInfo[] {
|
||||
return flatMapIter(refactors.values(), refactor =>
|
||||
context.cancellationToken && context.cancellationToken.isCancellationRequested() ? [] : refactor.getAvailableActions(context));
|
||||
context.cancellationToken && context.cancellationToken.isCancellationRequested() ? undefined : refactor.getAvailableActions(context));
|
||||
}
|
||||
|
||||
export function getEditsForRefactor(context: RefactorContext, refactorName: string, actionName: string): RefactorEditInfo | undefined {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user