@@ -708,17 +708,11 @@ namespace ts.server {
708
708
this . handleDeletedFile ( info ) ;
709
709
}
710
710
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 ) ;
722
716
}
723
717
}
724
718
@@ -851,15 +845,23 @@ namespace ts.server {
851
845
852
846
const project = this . getOrCreateInferredProjectForProjectRootPathIfEnabled ( info , projectRootPath ) ||
853
847
this . getOrCreateSingleInferredProjectIfEnabled ( ) ||
854
- this . createInferredProject ( info . isDynamic ? this . currentDirectory : getDirectoryPath ( info . path ) ) ;
848
+ this . getOrCreateSingleInferredWithoutProjectRoot ( info . isDynamic ? this . currentDirectory : getDirectoryPath ( info . path ) ) ;
855
849
856
850
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
+ }
857
856
project . updateGraph ( ) ;
858
857
859
858
if ( ! this . useSingleInferredProject && ! project . projectRootPath ) {
860
859
// 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
+
863
865
// Remove the inferred project if the root of it is now part of newly created inferred project
864
866
// e.g through references
865
867
// 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 {
870
872
// instead of scanning all open files
871
873
const roots = inferredProject . getRootScriptInfos ( ) ;
872
874
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 ) ;
875
877
}
876
878
}
877
879
}
@@ -896,9 +898,8 @@ namespace ts.server {
896
898
this . openFilesWithNonRootedDiskPath . delete ( canonicalFileName ) ;
897
899
}
898
900
899
-
900
901
// collect all projects that should be removed
901
- let projectsToRemove : Project [ ] ;
902
+ let ensureProjectsForOpenFiles = false ;
902
903
for ( const p of info . containingProjects ) {
903
904
if ( p . projectKind === ProjectKind . Configured ) {
904
905
if ( info . hasMixedContent ) {
@@ -908,15 +909,14 @@ namespace ts.server {
908
909
// if it would need to be re-created with next file open
909
910
}
910
911
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
912
913
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 ;
919
915
}
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
920
920
}
921
921
922
922
if ( ! p . languageServiceEnabled ) {
@@ -927,27 +927,22 @@ namespace ts.server {
927
927
}
928
928
}
929
929
930
- if ( projectsToRemove ) {
931
- for ( const project of projectsToRemove ) {
932
- this . removeProject ( project ) ;
933
- }
930
+ this . openFiles . delete ( info . path ) ;
934
931
932
+ if ( ensureProjectsForOpenFiles ) {
935
933
// collect orphaned files and assign them to inferred project just like we treat open of a file
936
934
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 ) ;
942
939
}
943
940
} ) ;
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
948
941
}
949
942
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
951
946
952
947
// If the current info is being just closed - add the watcher file to track changes
953
948
// But if file was deleted, handle that part
@@ -959,16 +954,6 @@ namespace ts.server {
959
954
}
960
955
}
961
956
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
-
972
957
private deleteScriptInfo ( info : ScriptInfo ) {
973
958
this . filenameToScriptInfo . delete ( info . path ) ;
974
959
const realpath = info . getRealpathIfDifferent ( ) ;
@@ -1673,6 +1658,21 @@ namespace ts.server {
1673
1658
return this . createInferredProject ( /*currentDirectory*/ undefined , /*isSingleInferredProject*/ true ) ;
1674
1659
}
1675
1660
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
+
1676
1676
private createInferredProject ( currentDirectory : string | undefined , isSingleInferredProject ?: boolean , projectRootPath ?: NormalizedPath ) : InferredProject {
1677
1677
const compilerOptions = projectRootPath && this . compilerOptionsForInferredProjectsPerProjectRoot . get ( projectRootPath ) || this . compilerOptionsForInferredProjects ;
1678
1678
const project = new InferredProject ( this , this . documentRegistry , compilerOptions , projectRootPath , currentDirectory ) ;
@@ -1719,6 +1719,7 @@ namespace ts.server {
1719
1719
for ( const project of toAddInfo . containingProjects ) {
1720
1720
// Add the projects only if they can use symLink targets and not already in the list
1721
1721
if ( project . languageServiceEnabled &&
1722
+ ! project . isOrphan ( ) &&
1722
1723
! project . getCompilerOptions ( ) . preserveSymlinks &&
1723
1724
! contains ( info . containingProjects , project ) ) {
1724
1725
if ( ! projects ) {
@@ -1947,16 +1948,14 @@ namespace ts.server {
1947
1948
// so it will be added to inferred project as a root. (for sake of this example assume single inferred project is false)
1948
1949
// So at this poing a.ts is part of first inferred project and second inferred project (of which c.ts is root)
1949
1950
// 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 ) ;
1960
1959
}
1961
1960
}
1962
1961
@@ -2050,9 +2049,9 @@ namespace ts.server {
2050
2049
if ( info . isOrphan ( ) ) {
2051
2050
this . assignOrphanScriptInfoToInferredProject ( info , projectRootPath ) ;
2052
2051
}
2053
-
2054
2052
Debug . assert ( ! info . isOrphan ( ) ) ;
2055
2053
2054
+
2056
2055
// Remove the configured projects that have zero references from open files.
2057
2056
// This was postponed from closeOpenFile to after opening next file,
2058
2057
// so that we can reuse the project if we need to right away
@@ -2062,11 +2061,26 @@ namespace ts.server {
2062
2061
}
2063
2062
} ) ;
2064
2063
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
+
2065
2072
// Delete the orphan files here because there might be orphan script infos (which are not part of project)
2066
2073
// when some file/s were closed which resulted in project removal.
2067
2074
// It was then postponed to cleanup these script infos so that they can be reused if
2068
2075
// 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
+
2070
2084
this . printProjects ( ) ;
2071
2085
2072
2086
return { configFileName, configFileErrors } ;
0 commit comments