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