@@ -258,7 +258,8 @@ namespace ts.server {
258
258
private compilerOptions : CompilerOptions ,
259
259
public compileOnSaveEnabled : boolean ,
260
260
directoryStructureHost : DirectoryStructureHost ,
261
- currentDirectory : string | undefined ) {
261
+ currentDirectory : string | undefined ,
262
+ customRealpath ?: ( s : string ) => string ) {
262
263
this . directoryStructureHost = directoryStructureHost ;
263
264
this . currentDirectory = this . projectService . getNormalizedAbsolutePath ( currentDirectory || "" ) ;
264
265
this . getCanonicalFileName = this . projectService . toCanonicalFileName ;
@@ -286,7 +287,7 @@ namespace ts.server {
286
287
}
287
288
288
289
if ( host . realpath ) {
289
- this . realpath = path => host . realpath ! ( path ) ;
290
+ this . realpath = customRealpath || ( path => host . realpath ! ( path ) ) ;
290
291
}
291
292
292
293
// Use the current directory as resolution root only if the project created using current directory string
@@ -1657,6 +1658,12 @@ namespace ts.server {
1657
1658
}
1658
1659
}
1659
1660
1661
+ /*@internal */
1662
+ interface SymlinkedDirectory {
1663
+ real : string ;
1664
+ realPath : Path ;
1665
+ }
1666
+
1660
1667
/**
1661
1668
* If a file is opened, the server will look for a tsconfig (or jsconfig)
1662
1669
* and if successfull create a ConfiguredProject for it.
@@ -1670,6 +1677,8 @@ namespace ts.server {
1670
1677
readonly canonicalConfigFilePath : NormalizedPath ;
1671
1678
private projectReferenceCallbacks : ResolvedProjectReferenceCallbacks | undefined ;
1672
1679
private mapOfDeclarationDirectories : Map < true > | undefined ;
1680
+ private symlinkedDirectories : Map < SymlinkedDirectory | false > | undefined ;
1681
+ private symlinkedFiles : Map < string > | undefined ;
1673
1682
1674
1683
/* @internal */
1675
1684
pendingReload : ConfigFileProgramReloadLevel | undefined ;
@@ -1711,7 +1720,9 @@ namespace ts.server {
1711
1720
/*compilerOptions*/ { } ,
1712
1721
/*compileOnSaveEnabled*/ false ,
1713
1722
cachedDirectoryStructureHost ,
1714
- getDirectoryPath ( configFileName ) ) ;
1723
+ getDirectoryPath ( configFileName ) ,
1724
+ projectService . host . realpath && ( s => this . getRealpath ( s ) )
1725
+ ) ;
1715
1726
this . canonicalConfigFilePath = asNormalizedPath ( projectService . toCanonicalFileName ( configFileName ) ) ;
1716
1727
}
1717
1728
@@ -1724,18 +1735,34 @@ namespace ts.server {
1724
1735
useSourceOfProjectReferenceRedirect = ( ) => ! ! this . languageServiceEnabled &&
1725
1736
! this . getCompilerOptions ( ) . disableSourceOfProjectReferenceRedirect ;
1726
1737
1738
+ private fileExistsIfProjectReferenceDts ( file : string ) {
1739
+ const source = this . projectReferenceCallbacks ! . getSourceOfProjectReferenceRedirect ( file ) ;
1740
+ return source !== undefined ?
1741
+ isString ( source ) ? super . fileExists ( source ) : true :
1742
+ undefined ;
1743
+ }
1744
+
1727
1745
/**
1728
1746
* This implementation of fileExists checks if the file being requested is
1729
1747
* .d.ts file for the referenced Project.
1730
1748
* If it is it returns true irrespective of whether that file exists on host
1731
1749
*/
1732
1750
fileExists ( file : string ) : boolean {
1751
+ if ( super . fileExists ( file ) ) return true ;
1752
+ if ( ! this . useSourceOfProjectReferenceRedirect ( ) || ! this . projectReferenceCallbacks ) return false ;
1753
+ if ( ! isDeclarationFileName ( file ) ) return false ;
1754
+
1733
1755
// Project references go to source file instead of .d.ts file
1734
- if ( this . useSourceOfProjectReferenceRedirect ( ) && this . projectReferenceCallbacks ) {
1735
- const source = this . projectReferenceCallbacks . getSourceOfProjectReferenceRedirect ( file ) ;
1736
- if ( source ) return isString ( source ) ? super . fileExists ( source ) : true ;
1737
- }
1738
- return super . fileExists ( file ) ;
1756
+ return this . fileOrDirectoryExistsUsingSource ( file , /*isFile*/ true ) ;
1757
+ }
1758
+
1759
+ private directoryExistsIfProjectReferenceDeclDir ( dir : string ) {
1760
+ const dirPath = this . toPath ( dir ) ;
1761
+ const dirPathWithTrailingDirectorySeparator = `${ dirPath } ${ directorySeparator } ` ;
1762
+ return forEachKey (
1763
+ this . mapOfDeclarationDirectories ! ,
1764
+ declDirPath => dirPath === declDirPath || startsWith ( declDirPath , dirPathWithTrailingDirectorySeparator )
1765
+ ) ;
1739
1766
}
1740
1767
1741
1768
/**
@@ -1744,14 +1771,17 @@ namespace ts.server {
1744
1771
* If it is it returns true irrespective of whether that directory exists on host
1745
1772
*/
1746
1773
directoryExists ( path : string ) : boolean {
1747
- if ( super . directoryExists ( path ) ) return true ;
1774
+ if ( super . directoryExists ( path ) ) {
1775
+ this . handleDirectoryCouldBeSymlink ( path ) ;
1776
+ return true ;
1777
+ }
1748
1778
if ( ! this . useSourceOfProjectReferenceRedirect ( ) || ! this . projectReferenceCallbacks ) return false ;
1749
1779
1750
1780
if ( ! this . mapOfDeclarationDirectories ) {
1751
1781
this . mapOfDeclarationDirectories = createMap ( ) ;
1752
1782
this . projectReferenceCallbacks . forEachResolvedProjectReference ( ref => {
1753
1783
if ( ! ref ) return ;
1754
- const out = ref . commandLine . options . outFile || ref . commandLine . options . outDir ;
1784
+ const out = ref . commandLine . options . outFile || ref . commandLine . options . out ;
1755
1785
if ( out ) {
1756
1786
this . mapOfDeclarationDirectories ! . set ( getDirectoryPath ( this . toPath ( out ) ) , true ) ;
1757
1787
}
@@ -1764,12 +1794,74 @@ namespace ts.server {
1764
1794
}
1765
1795
} ) ;
1766
1796
}
1767
- const dirPath = this . toPath ( path ) ;
1768
- const dirPathWithTrailingDirectorySeparator = `${ dirPath } ${ directorySeparator } ` ;
1769
- return ! ! forEachKey (
1770
- this . mapOfDeclarationDirectories ,
1771
- declDirPath => dirPath === declDirPath || startsWith ( declDirPath , dirPathWithTrailingDirectorySeparator )
1772
- ) ;
1797
+
1798
+ return this . fileOrDirectoryExistsUsingSource ( path , /*isFile*/ false ) ;
1799
+ }
1800
+
1801
+ private realpathIfSymlinkedProjectReferenceDts ( s : string ) : string | undefined {
1802
+ return this . symlinkedFiles && this . symlinkedFiles . get ( this . toPath ( s ) ) ;
1803
+ }
1804
+
1805
+ private getRealpath ( s : string ) : string {
1806
+ return this . realpathIfSymlinkedProjectReferenceDts ( s ) ||
1807
+ this . projectService . host . realpath ! ( s ) ;
1808
+ }
1809
+
1810
+ private handleDirectoryCouldBeSymlink ( directory : string ) {
1811
+ if ( ! this . useSourceOfProjectReferenceRedirect ( ) || ! this . projectReferenceCallbacks ) return ;
1812
+
1813
+ // Because we already watch node_modules, handle symlinks in there
1814
+ if ( ! this . realpath || ! stringContains ( directory , nodeModulesPathPart ) ) return ;
1815
+ if ( ! this . symlinkedDirectories ) this . symlinkedDirectories = createMap ( ) ;
1816
+ const directoryPath = ensureTrailingDirectorySeparator ( this . toPath ( directory ) ) ;
1817
+ if ( this . symlinkedDirectories . has ( directoryPath ) ) return ;
1818
+
1819
+ const real = this . projectService . host . realpath ! ( directory ) ;
1820
+ let realPath : Path ;
1821
+ if ( real === directory ||
1822
+ ( realPath = ensureTrailingDirectorySeparator ( this . toPath ( real ) ) ) === directoryPath ) {
1823
+ // not symlinked
1824
+ this . symlinkedDirectories . set ( directoryPath , false ) ;
1825
+ return ;
1826
+ }
1827
+
1828
+ this . symlinkedDirectories . set ( directoryPath , {
1829
+ real : ensureTrailingDirectorySeparator ( real ) ,
1830
+ realPath
1831
+ } ) ;
1832
+ }
1833
+
1834
+ private fileOrDirectoryExistsUsingSource ( fileOrDirectory : string , isFile : boolean ) : boolean {
1835
+ const fileOrDirectoryExistsUsingSource = isFile ?
1836
+ ( file : string ) => this . fileExistsIfProjectReferenceDts ( file ) :
1837
+ ( dir : string ) => this . directoryExistsIfProjectReferenceDeclDir ( dir ) ;
1838
+ // Check current directory or file
1839
+ const result = fileOrDirectoryExistsUsingSource ( fileOrDirectory ) ;
1840
+ if ( result !== undefined ) return result ;
1841
+
1842
+ if ( ! this . symlinkedDirectories ) return false ;
1843
+ const fileOrDirectoryPath = this . toPath ( fileOrDirectory ) ;
1844
+ if ( ! stringContains ( fileOrDirectoryPath , nodeModulesPathPart ) ) return false ;
1845
+ if ( isFile && this . symlinkedFiles && this . symlinkedFiles . has ( fileOrDirectoryPath ) ) return true ;
1846
+
1847
+ // If it contains node_modules check if its one of the symlinked path we know of
1848
+ return firstDefinedIterator (
1849
+ this . symlinkedDirectories . entries ( ) ,
1850
+ ( [ directoryPath , symlinkedDirectory ] ) => {
1851
+ if ( ! symlinkedDirectory || ! startsWith ( fileOrDirectoryPath , directoryPath ) ) return undefined ;
1852
+ const result = fileOrDirectoryExistsUsingSource ( fileOrDirectoryPath . replace ( directoryPath , symlinkedDirectory . realPath ) ) ;
1853
+ if ( isFile && result ) {
1854
+ if ( ! this . symlinkedFiles ) this . symlinkedFiles = createMap ( ) ;
1855
+ // Store the real path for the file'
1856
+ const absolutePath = getNormalizedAbsolutePath ( fileOrDirectory , this . currentDirectory ) ;
1857
+ this . symlinkedFiles . set (
1858
+ fileOrDirectoryPath ,
1859
+ `${ symlinkedDirectory . real } ${ absolutePath . replace ( new RegExp ( directoryPath , "i" ) , "" ) } `
1860
+ ) ;
1861
+ }
1862
+ return result ;
1863
+ }
1864
+ ) || false ;
1773
1865
}
1774
1866
1775
1867
/**
@@ -1782,6 +1874,8 @@ namespace ts.server {
1782
1874
this . pendingReload = ConfigFileProgramReloadLevel . None ;
1783
1875
this . projectReferenceCallbacks = undefined ;
1784
1876
this . mapOfDeclarationDirectories = undefined ;
1877
+ this . symlinkedDirectories = undefined ;
1878
+ this . symlinkedFiles = undefined ;
1785
1879
let result : boolean ;
1786
1880
switch ( reloadLevel ) {
1787
1881
case ConfigFileProgramReloadLevel . Partial :
@@ -1914,6 +2008,8 @@ namespace ts.server {
1914
2008
this . configFileSpecs = undefined ;
1915
2009
this . projectReferenceCallbacks = undefined ;
1916
2010
this . mapOfDeclarationDirectories = undefined ;
2011
+ this . symlinkedDirectories = undefined ;
2012
+ this . symlinkedFiles = undefined ;
1917
2013
super . close ( ) ;
1918
2014
}
1919
2015
0 commit comments