Some refactoring to combine files removal from inferred project

This commit is contained in:
Sheetal Nandi
2017-07-14 22:35:07 -07:00
parent 404aa8f0be
commit 71d79c62d0
3 changed files with 105 additions and 123 deletions

View File

@@ -245,20 +245,6 @@ namespace ts.server {
}
}
/**
* TODO: enforce invariants:
* - script info can be never migrate to state - root file in inferred project, this is only a starting point
* - if script info has more that one containing projects - it is not a root file in inferred project because:
* - references in inferred project supercede the root part
* - root/reference in non-inferred project beats root in inferred project
*/
function isRootFileInInferredProject(info: ScriptInfo): boolean {
if (info.containingProjects.length === 0) {
return false;
}
return info.containingProjects[0].projectKind === ProjectKind.Inferred && info.containingProjects[0].isRoot(info);
}
class DirectoryWatchers {
/**
* a path to directory watcher map that detects added tsconfig files
@@ -386,6 +372,7 @@ namespace ts.server {
private pendingProjectUpdates = createMap<Project>();
private pendingInferredProjectUpdate: boolean;
readonly currentDirectory: string;
readonly toCanonicalFileName: (f: string) => string;
public readonly host: ServerHost;
@@ -417,6 +404,7 @@ namespace ts.server {
Debug.assert(!!this.host.createHash, "'ServerHost.createHash' is required for ProjectService");
this.currentDirectory = this.host.getCurrentDirectory();
this.toCanonicalFileName = createGetCanonicalFileName(this.host.useCaseSensitiveFileNames);
this.directoryWatchers = new DirectoryWatchers(this);
this.throttledOperations = new ThrottledOperations(this.host);
@@ -431,7 +419,11 @@ namespace ts.server {
extraFileExtensions: []
};
this.documentRegistry = createDocumentRegistry(this.host.useCaseSensitiveFileNames, this.host.getCurrentDirectory());
this.documentRegistry = createDocumentRegistry(this.host.useCaseSensitiveFileNames, this.currentDirectory);
}
toPath(fileName: string, basePath = this.currentDirectory) {
return toPath(fileName, basePath, this.toCanonicalFileName);
}
/* @internal */
@@ -721,7 +713,7 @@ namespace ts.server {
this.removeProject(project);
// Reload the configured projects for these open files in the project as
// they could be held up by another config file somewhere in the parent directory
const openFilesInProject = this.getOrphanFiles();
const openFilesInProject = filter(this.openFiles, file => file.containingProjects.length === 0);
this.reloadConfiguredProjectForFiles(openFilesInProject, project => { project.pendingReload = true; this.delayUpdateProjectGraph(project); });
this.delayInferredProjectsRefresh();
}
@@ -762,6 +754,7 @@ namespace ts.server {
this.projectToSizeMap.delete((project as ExternalProject).externalProjectName);
break;
case ProjectKind.Configured:
// Update the map of mapOfKnownTsConfigFiles
removeItemFromSet(this.configuredProjects, <ConfiguredProject>project);
this.projectToSizeMap.delete((project as ConfiguredProject).canonicalConfigFilePath);
break;
@@ -772,59 +765,38 @@ namespace ts.server {
}
private assignScriptInfoToInferredProjectIfNecessary(info: ScriptInfo, addToListOfOpenFiles: boolean): void {
const externalProject = this.findContainingExternalProject(info.fileName);
if (externalProject) {
// file is already included in some external project - do nothing
if (addToListOfOpenFiles) {
this.openFiles.push(info);
}
return;
}
let foundConfiguredProject = false;
for (const p of info.containingProjects) {
// file is the part of configured project
if (p.projectKind === ProjectKind.Configured) {
foundConfiguredProject = true;
if (addToListOfOpenFiles) {
((<ConfiguredProject>p)).addOpenRef();
}
}
}
if (foundConfiguredProject) {
if (addToListOfOpenFiles) {
this.openFiles.push(info);
}
return;
}
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
const inferredProject = this.createInferredProjectWithRootFileIfNecessary(info);
if (!this.useSingleInferredProject) {
// 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
for (const f of this.openFiles) {
if (f.containingProjects.length === 0 || !inferredProject.containsScriptInfo(f)) {
// this is orphaned file that we have not processed yet - skip it
continue;
}
this.createInferredProjectWithRootFileIfNecessary(info);
for (const fContainingProject of f.containingProjects) {
if (fContainingProject.projectKind === ProjectKind.Inferred &&
fContainingProject.isRoot(f) &&
fContainingProject !== inferredProject) {
// open file used to be root in inferred project,
// this inferred project is different from the one we've just created for current file
// and new inferred project references this open file.
// We should delete old inferred project and attach open file to the new one
this.removeProject(fContainingProject);
f.attachToProject(inferredProject);
}
// 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();
}
}
}
@@ -835,17 +807,6 @@ namespace ts.server {
}
}
private getOrphanFiles() {
let orphanFiles: ScriptInfo[];
// for all open files
for (const f of this.openFiles) {
if (f.containingProjects.length === 0) {
(orphanFiles || (orphanFiles = [])).push(f);
}
}
return orphanFiles;
}
/**
* Remove this file from the set of open, non-configured files.
* @param info The file that has been closed or newly configured
@@ -871,8 +832,15 @@ namespace ts.server {
}
}
else if (p.projectKind === ProjectKind.Inferred && p.isRoot(info)) {
// open file in inferred project
(projectsToRemove || (projectsToRemove = [])).push(p);
// If this was the open root file of inferred project
if ((p as InferredProject).isProjectWithSingleRoot()) {
// - when useSingleInferredProject is not set, we can guarantee that this will be the only root
// - other wise remove the project if it is the only root
(projectsToRemove || (projectsToRemove = [])).push(p);
}
else {
p.removeFile(info);
}
}
if (!p.languageServiceEnabled) {
@@ -888,11 +856,10 @@ namespace ts.server {
}
// collect orphanted files and try to re-add them as newly opened
const orphanFiles = this.getOrphanFiles();
// treat orphaned files as newly opened
if (orphanFiles) {
for (const f of orphanFiles) {
// for all open files
for (const f of this.openFiles) {
if (f.containingProjects.length === 0) {
this.assignScriptInfoToInferredProjectIfNecessary(f, /*addToListOfOpenFiles*/ false);
}
}
@@ -1003,9 +970,9 @@ namespace ts.server {
private findConfiguredProjectByProjectName(configFileName: NormalizedPath) {
// make sure that casing of config file name is consistent
configFileName = asNormalizedPath(this.toCanonicalFileName(configFileName));
const canonicalConfigFilePath = asNormalizedPath(this.toCanonicalFileName(configFileName));
for (const proj of this.configuredProjects) {
if (proj.canonicalConfigFilePath === configFileName) {
if (proj.canonicalConfigFilePath === canonicalConfigFilePath) {
return proj;
}
}
@@ -1186,7 +1153,6 @@ namespace ts.server {
languageServiceEnabled,
projectOptions.compileOnSave === undefined ? false : projectOptions.compileOnSave,
cachedServerHost);
this.addFilesToProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typeAcquisition, configFileErrors);
project.configFileSpecs = configFileSpecs;
project.configFileWatcher = this.addFileWatcher(WatchType.ConfigFilePath, project,
@@ -1197,6 +1163,8 @@ namespace ts.server {
project.watchTypeRoots();
}
this.addFilesToProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typeAcquisition, configFileErrors);
this.configuredProjects.push(project);
this.sendProjectTelemetry(project.getConfigFilePath(), project, projectOptions);
return project;
@@ -1242,7 +1210,7 @@ namespace ts.server {
let scriptInfo: ScriptInfo | NormalizedPath;
let path: Path;
if (!project.lsHost.fileExists(newRootFile)) {
path = normalizedPathToPath(normalizedPath, this.host.getCurrentDirectory(), this.toCanonicalFileName);
path = normalizedPathToPath(normalizedPath, this.currentDirectory, this.toCanonicalFileName);
const existingValue = projectRootFilesMap.get(path);
if (isScriptInfo(existingValue)) {
project.removeFile(existingValue);
@@ -1257,16 +1225,12 @@ namespace ts.server {
path = scriptInfo.path;
// If this script info is not already a root add it
if (!project.isRoot(scriptInfo)) {
if (scriptInfo.isScriptOpen() && isRootFileInInferredProject(scriptInfo)) {
project.addRoot(scriptInfo);
if (scriptInfo.isScriptOpen()) {
// if file is already root in some inferred project
// - remove the file from that project and delete the project if necessary
const inferredProject = scriptInfo.containingProjects[0];
inferredProject.removeFile(scriptInfo);
if (!inferredProject.hasRoots()) {
this.removeProject(inferredProject);
}
this.removRootOfInferredProjectIfNowPartOfOtherProject(scriptInfo);
}
project.addRoot(scriptInfo);
}
}
newRootScriptInfoMap.set(path, scriptInfo);
@@ -1362,7 +1326,6 @@ namespace ts.server {
root.fileName,
project,
fileName => this.onConfigFileAddedForInferredProject(fileName));
project.updateGraph();
if (!useExistingProject) {
@@ -1403,7 +1366,7 @@ namespace ts.server {
}
getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean) {
const path = toPath(fileName, this.host.getCurrentDirectory(), this.toCanonicalFileName);
const path = normalizedPathToPath(fileName, this.currentDirectory, this.toCanonicalFileName);
let info = this.getScriptInfoForPath(path);
if (!info) {
if (openedByClient || this.host.fileExists(fileName)) {
@@ -1438,7 +1401,7 @@ namespace ts.server {
}
getScriptInfoForNormalizedPath(fileName: NormalizedPath) {
return this.getScriptInfoForPath(normalizedPathToPath(fileName, this.host.getCurrentDirectory(), this.toCanonicalFileName));
return this.getScriptInfoForPath(normalizedPathToPath(fileName, this.currentDirectory, this.toCanonicalFileName));
}
getScriptInfoForPath(fileName: Path) {
@@ -1539,6 +1502,26 @@ namespace ts.server {
}
}
/**
* - script info can be never migrate to state - root file in inferred project, this is only a starting point
* - if script info has more that one containing projects - it is not a root file in inferred project because:
* - references in inferred project supercede the root part
* - root/reference in non-inferred project beats root in inferred project
*/
private removRootOfInferredProjectIfNowPartOfOtherProject(info: ScriptInfo) {
if (info.containingProjects.length > 1 &&
info.containingProjects[0].projectKind === ProjectKind.Inferred &&
info.containingProjects[0].isRoot(info)) {
const inferredProject = info.containingProjects[0] as InferredProject;
if (inferredProject.isProjectWithSingleRoot()) {
this.removeProject(inferredProject);
}
else {
inferredProject.removeFile(info);
}
}
}
/**
* This function is to update the project structure for every projects.
* It is called on the premise that all the configured projects are
@@ -1548,26 +1531,16 @@ namespace ts.server {
this.logger.info("updating project structure from ...");
this.printProjects();
const orphantedFiles: ScriptInfo[] = [];
// collect all orphanted script infos from open files
for (const info of this.openFiles) {
// collect all orphanted script infos from open files
if (info.containingProjects.length === 0) {
orphantedFiles.push(info);
this.assignScriptInfoToInferredProjectIfNecessary(info, /*addToListOfOpenFiles*/ false);
}
// Or remove the root of inferred project if is referenced in more than one projects
else {
if (isRootFileInInferredProject(info) && info.containingProjects.length > 1) {
const inferredProject = info.containingProjects[0];
Debug.assert(inferredProject.projectKind === ProjectKind.Inferred);
inferredProject.removeFile(info);
if (!inferredProject.hasRoots()) {
this.removeProject(inferredProject);
}
}
this.removRootOfInferredProjectIfNowPartOfOtherProject(info);
}
}
for (const f of orphantedFiles) {
this.assignScriptInfoToInferredProjectIfNecessary(f, /*addToListOfOpenFiles*/ false);
}
for (const p of this.inferredProjects) {
p.updateGraph();

View File

@@ -27,8 +27,12 @@ namespace ts.server {
this.currentDirectory = this.host.getCurrentDirectory();
}
private toPath(fileName: string) {
return toPath(fileName, this.currentDirectory, this.getCanonicalFileName);
}
private getFileSystemEntries(rootDir: string) {
const path = toPath(rootDir, this.currentDirectory, this.getCanonicalFileName);
const path = this.toPath(rootDir);
const cachedResult = this.cachedReadDirectoryResult.get(path);
if (cachedResult) {
return cachedResult;
@@ -45,7 +49,7 @@ namespace ts.server {
private canWorkWithCacheForDir(rootDir: string) {
// Some of the hosts might not be able to handle read directory or getDirectories
const path = toPath(rootDir, this.currentDirectory, this.getCanonicalFileName);
const path = this.toPath(rootDir);
if (this.cachedReadDirectoryResult.get(path)) {
return true;
}
@@ -62,7 +66,7 @@ namespace ts.server {
}
writeFile(fileName: string, data: string, writeByteOrderMark?: boolean) {
const path = toPath(fileName, this.currentDirectory, this.getCanonicalFileName);
const path = this.toPath(fileName);
const result = this.cachedReadDirectoryResult.get(getDirectoryPath(path));
const baseFileName = getBaseFileName(toNormalizedPath(fileName));
if (result) {
@@ -111,14 +115,14 @@ namespace ts.server {
}
fileExists(fileName: string): boolean {
const path = toPath(fileName, this.currentDirectory, this.getCanonicalFileName);
const path = this.toPath(fileName);
const result = this.cachedReadDirectoryResult.get(getDirectoryPath(path));
const baseName = getBaseFileName(toNormalizedPath(fileName));
return (result && this.hasEntry(result.files, baseName)) || this.host.fileExists(fileName);
}
directoryExists(dirPath: string) {
const path = toPath(dirPath, this.currentDirectory, this.getCanonicalFileName);
const path = this.toPath(dirPath);
return this.cachedReadDirectoryResult.has(path) || this.host.directoryExists(dirPath);
}
@@ -147,7 +151,7 @@ namespace ts.server {
}
addOrDeleteFileOrFolder(fileOrFolder: NormalizedPath) {
const path = toPath(fileOrFolder, this.currentDirectory, this.getCanonicalFileName);
const path = this.toPath(fileOrFolder);
const existingResult = this.cachedReadDirectoryResult.get(path);
if (existingResult) {
if (!this.host.directoryExists(fileOrFolder)) {
@@ -262,7 +266,7 @@ namespace ts.server {
getResultFileName: (result: R) => string | undefined,
logChanges: boolean): R[] {
const path = toPath(containingFile, this.host.getCurrentDirectory(), this.project.projectService.toCanonicalFileName);
const path = this.project.projectService.toPath(containingFile);
const currentResolutionsInFile = cache.get(path);
const newResolutions: Map<T> = createMap<T>();
@@ -403,7 +407,7 @@ namespace ts.server {
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.project.projectService.toCanonicalFileName);
const path = this.project.projectService.toPath(file);
return !this.project.isWatchedMissingFile(path) && this.host.fileExists(file);
}

View File

@@ -496,8 +496,7 @@ namespace ts.server {
// add a root file to project
addMissingFileRoot(fileName: NormalizedPath) {
const path = toPath(fileName, this.projectService.host.getCurrentDirectory(),
this.projectService.toCanonicalFileName);
const path = this.projectService.toPath(fileName);
this.rootFilesMap.set(path, fileName);
this.markAsDirty();
}
@@ -842,7 +841,7 @@ namespace ts.server {
// Handle triple slash references
if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) {
for (const referencedFile of sourceFile.referencedFiles) {
const referencedPath = toPath(referencedFile.fileName, currentDirectory, this.projectService.toCanonicalFileName);
const referencedPath = this.projectService.toPath(referencedFile.fileName, currentDirectory);
referencedFiles.set(referencedPath, true);
}
}
@@ -855,7 +854,7 @@ namespace ts.server {
}
const fileName = resolvedTypeReferenceDirective.resolvedFileName;
const typeFilePath = toPath(fileName, currentDirectory, this.projectService.toCanonicalFileName);
const typeFilePath = this.projectService.toPath(fileName, currentDirectory);
referencedFiles.set(typeFilePath, true);
});
}
@@ -943,6 +942,12 @@ namespace ts.server {
}
}
isProjectWithSingleRoot() {
// - when useSingleInferredProject is not set, we can guarantee that this will be the only root
// - other wise it has single root if it has single root script info
return !this.projectService.useSingleInferredProject || this.getRootScriptInfos().length === 1;
}
getProjectRootPath() {
// Single inferred project does not have a project root.
if (this.projectService.useSingleInferredProject) {
@@ -1143,7 +1148,7 @@ namespace ts.server {
const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0;
return existingRecursive !== recursive;
},
// Create new watch
// Create new watch and recursive info
(directory, flag) => {
const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0;
return {