@@ -198,16 +198,6 @@ namespace ts.server {
198
198
}
199
199
}
200
200
201
- /**
202
- * This helper function processes a list of projects and return the concatenated, sortd and deduplicated output of processing each project.
203
- */
204
- export function combineProjectOutput < T > ( projects : ReadonlyArray < Project > , action : ( project : Project ) => ReadonlyArray < T > , comparer ?: ( a : T , b : T ) => number , areEqual ?: ( a : T , b : T ) => boolean ) {
205
- const outputs = flatMap ( projects , action ) ;
206
- return comparer
207
- ? sortAndDeduplicate ( outputs , comparer , areEqual )
208
- : deduplicate ( outputs , areEqual ) ;
209
- }
210
-
211
201
export interface HostConfiguration {
212
202
formatCodeOptions : FormatCodeSettings ;
213
203
hostInfo : string ;
@@ -335,6 +325,11 @@ namespace ts.server {
335
325
* Container of all known scripts
336
326
*/
337
327
private readonly filenameToScriptInfo = createMap < ScriptInfo > ( ) ;
328
+ /**
329
+ * Map to the real path of the infos
330
+ */
331
+ /* @internal */
332
+ readonly realpathToScriptInfos : MultiMap < ScriptInfo > | undefined ;
338
333
/**
339
334
* maps external project file name to list of config files that were the part of this project
340
335
*/
@@ -427,7 +422,9 @@ namespace ts.server {
427
422
this . typesMapLocation = ( opts . typesMapLocation === undefined ) ? combinePaths ( this . getExecutingFilePath ( ) , "../typesMap.json" ) : opts . typesMapLocation ;
428
423
429
424
Debug . assert ( ! ! this . host . createHash , "'ServerHost.createHash' is required for ProjectService" ) ;
430
-
425
+ if ( this . host . realpath ) {
426
+ this . realpathToScriptInfos = createMultiMap ( ) ;
427
+ }
431
428
this . currentDirectory = this . host . getCurrentDirectory ( ) ;
432
429
this . toCanonicalFileName = createGetCanonicalFileName ( this . host . useCaseSensitiveFileNames ) ;
433
430
this . throttledOperations = new ThrottledOperations ( this . host , this . logger ) ;
@@ -768,7 +765,7 @@ namespace ts.server {
768
765
if ( info . containingProjects . length === 0 ) {
769
766
// Orphan script info, remove it as we can always reload it on next open file request
770
767
this . stopWatchingScriptInfo ( info ) ;
771
- this . filenameToScriptInfo . delete ( info . path ) ;
768
+ this . deleteScriptInfo ( info ) ;
772
769
}
773
770
else {
774
771
// file has been changed which might affect the set of referenced files in projects that include
@@ -785,7 +782,7 @@ namespace ts.server {
785
782
// TODO: handle isOpen = true case
786
783
787
784
if ( ! info . isScriptOpen ( ) ) {
788
- this . filenameToScriptInfo . delete ( info . path ) ;
785
+ this . deleteScriptInfo ( info ) ;
789
786
790
787
// capture list of projects since detachAllProjects will wipe out original list
791
788
const containingProjects = info . containingProjects . slice ( ) ;
@@ -1019,11 +1016,19 @@ namespace ts.server {
1019
1016
if ( ! info . isScriptOpen ( ) && info . isOrphan ( ) ) {
1020
1017
// if there are not projects that include this script info - delete it
1021
1018
this . stopWatchingScriptInfo ( info ) ;
1022
- this . filenameToScriptInfo . delete ( info . path ) ;
1019
+ this . deleteScriptInfo ( info ) ;
1023
1020
}
1024
1021
} ) ;
1025
1022
}
1026
1023
1024
+ private deleteScriptInfo ( info : ScriptInfo ) {
1025
+ this . filenameToScriptInfo . delete ( info . path ) ;
1026
+ const realpath = info . getRealpathIfDifferent ( ) ;
1027
+ if ( realpath ) {
1028
+ this . realpathToScriptInfos . remove ( realpath , info ) ;
1029
+ }
1030
+ }
1031
+
1027
1032
private configFileExists ( configFileName : NormalizedPath , canonicalConfigFilePath : string , info : ScriptInfo ) {
1028
1033
let configFileExistenceInfo = this . configFileExistenceInfoCache . get ( canonicalConfigFilePath ) ;
1029
1034
if ( configFileExistenceInfo ) {
@@ -1736,6 +1741,43 @@ namespace ts.server {
1736
1741
return this . getScriptInfoForNormalizedPath ( toNormalizedPath ( uncheckedFileName ) ) ;
1737
1742
}
1738
1743
1744
+ /**
1745
+ * Returns the projects that contain script info through SymLink
1746
+ * Note that this does not return projects in info.containingProjects
1747
+ */
1748
+ /*@internal */
1749
+ getSymlinkedProjects ( info : ScriptInfo ) : MultiMap < Project > | undefined {
1750
+ let projects : MultiMap < Project > | undefined ;
1751
+ if ( this . realpathToScriptInfos ) {
1752
+ const realpath = info . getRealpathIfDifferent ( ) ;
1753
+ if ( realpath ) {
1754
+ forEach ( this . realpathToScriptInfos . get ( realpath ) , combineProjects ) ;
1755
+ }
1756
+ forEach ( this . realpathToScriptInfos . get ( info . path ) , combineProjects ) ;
1757
+ }
1758
+
1759
+ return projects ;
1760
+
1761
+ function combineProjects ( toAddInfo : ScriptInfo ) {
1762
+ if ( toAddInfo !== info ) {
1763
+ for ( const project of toAddInfo . containingProjects ) {
1764
+ // Add the projects only if they can use symLink targets and not already in the list
1765
+ if ( project . languageServiceEnabled &&
1766
+ ! project . getCompilerOptions ( ) . preserveSymlinks &&
1767
+ ! contains ( info . containingProjects , project ) ) {
1768
+ if ( ! projects ) {
1769
+ projects = createMultiMap ( ) ;
1770
+ projects . add ( toAddInfo . path , project ) ;
1771
+ }
1772
+ else if ( ! forEachEntry ( projects , ( projs , path ) => path === toAddInfo . path ? false : contains ( projs , project ) ) ) {
1773
+ projects . add ( toAddInfo . path , project ) ;
1774
+ }
1775
+ }
1776
+ }
1777
+ }
1778
+ }
1779
+ }
1780
+
1739
1781
private watchClosedScriptInfo ( info : ScriptInfo ) {
1740
1782
Debug . assert ( ! info . fileWatcher ) ;
1741
1783
// do not watch files with mixed content - server doesn't know how to interpret it
0 commit comments