@@ -708,17 +708,11 @@ namespace ts.server {
708708 this . handleDeletedFile ( info ) ;
709709 }
710710 else if ( ! info . isScriptOpen ( ) ) {
711- if ( info . containingProjects . length === 0 ) {
712- // Orphan script info, remove it as we can always reload it on next open file request
713- this . stopWatchingScriptInfo ( info ) ;
714- this . deleteScriptInfo ( info ) ;
715- }
716- else {
717- // file has been changed which might affect the set of referenced files in projects that include
718- // this file and set of inferred projects
719- info . delayReloadNonMixedContentFile ( ) ;
720- this . delayUpdateProjectGraphs ( info . containingProjects ) ;
721- }
711+ Debug . assert ( info . containingProjects . length !== 0 ) ;
712+ // file has been changed which might affect the set of referenced files in projects that include
713+ // this file and set of inferred projects
714+ info . delayReloadNonMixedContentFile ( ) ;
715+ this . delayUpdateProjectGraphs ( info . containingProjects ) ;
722716 }
723717 }
724718
@@ -851,15 +845,23 @@ namespace ts.server {
851845
852846 const project = this . getOrCreateInferredProjectForProjectRootPathIfEnabled ( info , projectRootPath ) ||
853847 this . getOrCreateSingleInferredProjectIfEnabled ( ) ||
854- this . createInferredProject ( info . isDynamic ? this . currentDirectory : getDirectoryPath ( info . path ) ) ;
848+ this . getOrCreateSingleInferredWithoutProjectRoot ( info . isDynamic ? this . currentDirectory : getDirectoryPath ( info . path ) ) ;
855849
856850 project . addRoot ( info ) ;
851+ if ( info . containingProjects [ 0 ] !== project ) {
852+ // Ensure this is first project, we could be in this scenario because info could be part of orphan project
853+ info . detachFromProject ( project ) ;
854+ info . containingProjects . unshift ( project ) ;
855+ }
857856 project . updateGraph ( ) ;
858857
859858 if ( ! this . useSingleInferredProject && ! project . projectRootPath ) {
860859 // Note that we need to create a copy of the array since the list of project can change
861- for ( const inferredProject of this . inferredProjects . slice ( 0 , this . inferredProjects . length - 1 ) ) {
862- Debug . assert ( inferredProject !== project ) ;
860+ for ( const inferredProject of this . inferredProjects ) {
861+ if ( inferredProject === project || inferredProject . isOrphan ( ) ) {
862+ continue ;
863+ }
864+
863865 // Remove the inferred project if the root of it is now part of newly created inferred project
864866 // e.g through references
865867 // Which means if any root of inferred project is part of more than 1 project can be removed
@@ -870,8 +872,8 @@ namespace ts.server {
870872 // instead of scanning all open files
871873 const roots = inferredProject . getRootScriptInfos ( ) ;
872874 Debug . assert ( roots . length === 1 || ! ! inferredProject . projectRootPath ) ;
873- if ( roots . length === 1 && roots [ 0 ] . containingProjects . length > 1 ) {
874- this . removeProject ( inferredProject ) ;
875+ if ( roots . length === 1 && forEach ( roots [ 0 ] . containingProjects , p => p !== roots [ 0 ] . containingProjects [ 0 ] && ! p . isOrphan ( ) ) ) {
876+ inferredProject . removeFile ( roots [ 0 ] , /*fileExists*/ true , /*detachFromProject*/ true ) ;
875877 }
876878 }
877879 }
@@ -896,9 +898,8 @@ namespace ts.server {
896898 this . openFilesWithNonRootedDiskPath . delete ( canonicalFileName ) ;
897899 }
898900
899-
900901 // collect all projects that should be removed
901- let projectsToRemove : Project [ ] ;
902+ let ensureProjectsForOpenFiles = false ;
902903 for ( const p of info . containingProjects ) {
903904 if ( p . projectKind === ProjectKind . Configured ) {
904905 if ( info . hasMixedContent ) {
@@ -908,15 +909,14 @@ namespace ts.server {
908909 // if it would need to be re-created with next file open
909910 }
910911 else if ( p . projectKind === ProjectKind . Inferred && p . isRoot ( info ) ) {
911- // If this was the open root file of inferred project
912+ // If this was the last open root file of inferred project
912913 if ( ( p as InferredProject ) . isProjectWithSingleRoot ( ) ) {
913- // - when useSingleInferredProject is not set, we can guarantee that this will be the only root
914- // - other wise remove the project if it is the only root
915- ( projectsToRemove || ( projectsToRemove = [ ] ) ) . push ( p ) ;
916- }
917- else {
918- p . removeFile ( info , fileExists , /*detachFromProject*/ true ) ;
914+ ensureProjectsForOpenFiles = true ;
919915 }
916+
917+ p . removeFile ( info , fileExists , /*detachFromProject*/ true ) ;
918+ // Do not remove the project even if this was last root of the inferred project
919+ // so that we can reuse this project, if it would need to be re-created with next file open
920920 }
921921
922922 if ( ! p . languageServiceEnabled ) {
@@ -927,27 +927,22 @@ namespace ts.server {
927927 }
928928 }
929929
930- if ( projectsToRemove ) {
931- for ( const project of projectsToRemove ) {
932- this . removeProject ( project ) ;
933- }
930+ this . openFiles . delete ( info . path ) ;
934931
932+ if ( ensureProjectsForOpenFiles ) {
935933 // collect orphaned files and assign them to inferred project just like we treat open of a file
936934 this . openFiles . forEach ( ( projectRootPath , path ) => {
937- if ( info . path !== path ) {
938- const f = this . getScriptInfoForPath ( path as Path ) ;
939- if ( f . isOrphan ( ) ) {
940- this . assignOrphanScriptInfoToInferredProject ( f , projectRootPath ) ;
941- }
935+ const info = this . getScriptInfoForPath ( path as Path ) ;
936+ // collect all orphaned script infos from open files
937+ if ( info . isOrphan ( ) ) {
938+ this . assignOrphanScriptInfoToInferredProject ( info , projectRootPath ) ;
942939 }
943940 } ) ;
944-
945- // Cleanup script infos that arent part of any project (eg. those could be closed script infos not referenced by any project)
946- // is postponed to next file open so that if file from same project is opened,
947- // we wont end up creating same script infos
948941 }
949942
950- this . openFiles . delete ( info . path ) ;
943+ // Cleanup script infos that arent part of any project (eg. those could be closed script infos not referenced by any project)
944+ // is postponed to next file open so that if file from same project is opened,
945+ // we wont end up creating same script infos
951946
952947 // If the current info is being just closed - add the watcher file to track changes
953948 // But if file was deleted, handle that part
@@ -959,16 +954,6 @@ namespace ts.server {
959954 }
960955 }
961956
962- private deleteOrphanScriptInfoNotInAnyProject ( ) {
963- this . filenameToScriptInfo . forEach ( info => {
964- if ( ! info . isScriptOpen ( ) && info . isOrphan ( ) ) {
965- // if there are not projects that include this script info - delete it
966- this . stopWatchingScriptInfo ( info ) ;
967- this . deleteScriptInfo ( info ) ;
968- }
969- } ) ;
970- }
971-
972957 private deleteScriptInfo ( info : ScriptInfo ) {
973958 this . filenameToScriptInfo . delete ( info . path ) ;
974959 const realpath = info . getRealpathIfDifferent ( ) ;
@@ -1673,6 +1658,21 @@ namespace ts.server {
16731658 return this . createInferredProject ( /*currentDirectory*/ undefined , /*isSingleInferredProject*/ true ) ;
16741659 }
16751660
1661+ private getOrCreateSingleInferredWithoutProjectRoot ( currentDirectory : string | undefined ) : InferredProject {
1662+ Debug . assert ( ! this . useSingleInferredProject ) ;
1663+ const expectedCurrentDirectory = this . toCanonicalFileName ( this . getNormalizedAbsolutePath ( currentDirectory || "" ) ) ;
1664+ // Reuse the project with same current directory but no roots
1665+ for ( const inferredProject of this . inferredProjects ) {
1666+ if ( ! inferredProject . projectRootPath &&
1667+ inferredProject . isOrphan ( ) &&
1668+ inferredProject . canonicalCurrentDirectory === expectedCurrentDirectory ) {
1669+ return inferredProject ;
1670+ }
1671+ }
1672+
1673+ return this . createInferredProject ( currentDirectory ) ;
1674+ }
1675+
16761676 private createInferredProject ( currentDirectory : string | undefined , isSingleInferredProject ?: boolean , projectRootPath ?: NormalizedPath ) : InferredProject {
16771677 const compilerOptions = projectRootPath && this . compilerOptionsForInferredProjectsPerProjectRoot . get ( projectRootPath ) || this . compilerOptionsForInferredProjects ;
16781678 const project = new InferredProject ( this , this . documentRegistry , compilerOptions , projectRootPath , currentDirectory ) ;
@@ -1719,6 +1719,7 @@ namespace ts.server {
17191719 for ( const project of toAddInfo . containingProjects ) {
17201720 // Add the projects only if they can use symLink targets and not already in the list
17211721 if ( project . languageServiceEnabled &&
1722+ ! project . isOrphan ( ) &&
17221723 ! project . getCompilerOptions ( ) . preserveSymlinks &&
17231724 ! contains ( info . containingProjects , project ) ) {
17241725 if ( ! projects ) {
@@ -1947,16 +1948,14 @@ namespace ts.server {
19471948 // so it will be added to inferred project as a root. (for sake of this example assume single inferred project is false)
19481949 // So at this poing a.ts is part of first inferred project and second inferred project (of which c.ts is root)
19491950 // And hence it needs to be removed from the first inferred project.
1950- if ( info . containingProjects . length > 1 &&
1951- info . containingProjects [ 0 ] . projectKind === ProjectKind . Inferred &&
1952- info . containingProjects [ 0 ] . isRoot ( info ) ) {
1953- const inferredProject = info . containingProjects [ 0 ] as InferredProject ;
1954- if ( inferredProject . isProjectWithSingleRoot ( ) ) {
1955- this . removeProject ( inferredProject ) ;
1956- }
1957- else {
1958- inferredProject . removeFile ( info , /*fileExists*/ true , /*detachFromProject*/ true ) ;
1959- }
1951+ Debug . assert ( info . containingProjects . length > 0 ) ;
1952+ const firstProject = info . containingProjects [ 0 ] ;
1953+
1954+ if ( ! firstProject . isOrphan ( ) &&
1955+ firstProject . projectKind === ProjectKind . Inferred &&
1956+ firstProject . isRoot ( info ) &&
1957+ forEach ( info . containingProjects , p => p !== firstProject && ! p . isOrphan ( ) ) ) {
1958+ firstProject . removeFile ( info , /*fileExists*/ true , /*detachFromProject*/ true ) ;
19601959 }
19611960 }
19621961
@@ -2050,9 +2049,9 @@ namespace ts.server {
20502049 if ( info . isOrphan ( ) ) {
20512050 this . assignOrphanScriptInfoToInferredProject ( info , projectRootPath ) ;
20522051 }
2053-
20542052 Debug . assert ( ! info . isOrphan ( ) ) ;
20552053
2054+
20562055 // Remove the configured projects that have zero references from open files.
20572056 // This was postponed from closeOpenFile to after opening next file,
20582057 // so that we can reuse the project if we need to right away
@@ -2062,11 +2061,26 @@ namespace ts.server {
20622061 }
20632062 } ) ;
20642063
2064+ // Remove orphan inferred projects now that we have reused projects
2065+ // We need to create a duplicate because we cant guarantee order after removal
2066+ for ( const inferredProject of this . inferredProjects . slice ( ) ) {
2067+ if ( inferredProject . isOrphan ( ) ) {
2068+ this . removeProject ( inferredProject ) ;
2069+ }
2070+ }
2071+
20652072 // Delete the orphan files here because there might be orphan script infos (which are not part of project)
20662073 // when some file/s were closed which resulted in project removal.
20672074 // It was then postponed to cleanup these script infos so that they can be reused if
20682075 // the file from that old project is reopened because of opening file from here.
2069- this . deleteOrphanScriptInfoNotInAnyProject ( ) ;
2076+ this . filenameToScriptInfo . forEach ( info => {
2077+ if ( ! info . isScriptOpen ( ) && info . isOrphan ( ) ) {
2078+ // if there are not projects that include this script info - delete it
2079+ this . stopWatchingScriptInfo ( info ) ;
2080+ this . deleteScriptInfo ( info ) ;
2081+ }
2082+ } ) ;
2083+
20702084 this . printProjects ( ) ;
20712085
20722086 return { configFileName, configFileErrors } ;
0 commit comments