@@ -198,16 +198,6 @@ namespace ts.server {
198198 }
199199 }
200200
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-
211201 export interface HostConfiguration {
212202 formatCodeOptions : FormatCodeSettings ;
213203 hostInfo : string ;
@@ -335,6 +325,11 @@ namespace ts.server {
335325 * Container of all known scripts
336326 */
337327 private readonly filenameToScriptInfo = createMap < ScriptInfo > ( ) ;
328+ /**
329+ * Map to the real path of the infos
330+ */
331+ /* @internal */
332+ readonly realpathToScriptInfos : MultiMap < ScriptInfo > | undefined ;
338333 /**
339334 * maps external project file name to list of config files that were the part of this project
340335 */
@@ -427,7 +422,9 @@ namespace ts.server {
427422 this . typesMapLocation = ( opts . typesMapLocation === undefined ) ? combinePaths ( this . getExecutingFilePath ( ) , "../typesMap.json" ) : opts . typesMapLocation ;
428423
429424 Debug . assert ( ! ! this . host . createHash , "'ServerHost.createHash' is required for ProjectService" ) ;
430-
425+ if ( this . host . realpath ) {
426+ this . realpathToScriptInfos = createMultiMap ( ) ;
427+ }
431428 this . currentDirectory = this . host . getCurrentDirectory ( ) ;
432429 this . toCanonicalFileName = createGetCanonicalFileName ( this . host . useCaseSensitiveFileNames ) ;
433430 this . throttledOperations = new ThrottledOperations ( this . host , this . logger ) ;
@@ -768,7 +765,7 @@ namespace ts.server {
768765 if ( info . containingProjects . length === 0 ) {
769766 // Orphan script info, remove it as we can always reload it on next open file request
770767 this . stopWatchingScriptInfo ( info ) ;
771- this . filenameToScriptInfo . delete ( info . path ) ;
768+ this . deleteScriptInfo ( info ) ;
772769 }
773770 else {
774771 // file has been changed which might affect the set of referenced files in projects that include
@@ -785,7 +782,7 @@ namespace ts.server {
785782 // TODO: handle isOpen = true case
786783
787784 if ( ! info . isScriptOpen ( ) ) {
788- this . filenameToScriptInfo . delete ( info . path ) ;
785+ this . deleteScriptInfo ( info ) ;
789786
790787 // capture list of projects since detachAllProjects will wipe out original list
791788 const containingProjects = info . containingProjects . slice ( ) ;
@@ -1019,11 +1016,19 @@ namespace ts.server {
10191016 if ( ! info . isScriptOpen ( ) && info . isOrphan ( ) ) {
10201017 // if there are not projects that include this script info - delete it
10211018 this . stopWatchingScriptInfo ( info ) ;
1022- this . filenameToScriptInfo . delete ( info . path ) ;
1019+ this . deleteScriptInfo ( info ) ;
10231020 }
10241021 } ) ;
10251022 }
10261023
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+
10271032 private configFileExists ( configFileName : NormalizedPath , canonicalConfigFilePath : string , info : ScriptInfo ) {
10281033 let configFileExistenceInfo = this . configFileExistenceInfoCache . get ( canonicalConfigFilePath ) ;
10291034 if ( configFileExistenceInfo ) {
@@ -1736,6 +1741,43 @@ namespace ts.server {
17361741 return this . getScriptInfoForNormalizedPath ( toNormalizedPath ( uncheckedFileName ) ) ;
17371742 }
17381743
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+
17391781 private watchClosedScriptInfo ( info : ScriptInfo ) {
17401782 Debug . assert ( ! info . fileWatcher ) ;
17411783 // do not watch files with mixed content - server doesn't know how to interpret it
0 commit comments