@@ -291,7 +291,8 @@ namespace ts.server {
291
291
ClosedScriptInfo = "Closed Script info" ,
292
292
ConfigFileForInferredRoot = "Config file for the inferred project root" ,
293
293
FailedLookupLocation = "Directory of Failed lookup locations in module resolution" ,
294
- TypeRoots = "Type root directory"
294
+ TypeRoots = "Type root directory" ,
295
+ NodeModulesForClosedScriptInfo = "node_modules for closed script infos in them" ,
295
296
}
296
297
297
298
const enum ConfigFileWatcherStatus {
@@ -353,10 +354,18 @@ namespace ts.server {
353
354
return ! ! ( infoOrFileName as ScriptInfo ) . containingProjects ;
354
355
}
355
356
357
+ interface ScriptInfoInNodeModulesWatcher extends FileWatcher {
358
+ refCount : number ;
359
+ }
360
+
356
361
function getDetailWatchInfo ( watchType : WatchType , project : Project | undefined ) {
357
362
return `Project: ${ project ? project . getProjectName ( ) : "" } WatchType: ${ watchType } ` ;
358
363
}
359
364
365
+ function isScriptInfoWatchedFromNodeModules ( info : ScriptInfo ) {
366
+ return ! info . isScriptOpen ( ) && info . mTime !== undefined ;
367
+ }
368
+
360
369
/*@internal */
361
370
export function updateProjectIfDirty ( project : Project ) {
362
371
return project . dirty && project . updateGraph ( ) ;
@@ -380,6 +389,7 @@ namespace ts.server {
380
389
* Container of all known scripts
381
390
*/
382
391
private readonly filenameToScriptInfo = createMap < ScriptInfo > ( ) ;
392
+ private readonly scriptInfoInNodeModulesWatchers = createMap < ScriptInfoInNodeModulesWatcher > ( ) ;
383
393
/**
384
394
* Contains all the deleted script info's version information so that
385
395
* it does not reset when creating script info again
@@ -1923,18 +1933,97 @@ namespace ts.server {
1923
1933
if ( ! info . isDynamicOrHasMixedContent ( ) &&
1924
1934
( ! this . globalCacheLocationDirectoryPath ||
1925
1935
! startsWith ( info . path , this . globalCacheLocationDirectoryPath ) ) ) {
1926
- const { fileName } = info ;
1927
- info . fileWatcher = this . watchFactory . watchFilePath (
1928
- this . host ,
1929
- fileName ,
1930
- ( fileName , eventKind , path ) => this . onSourceFileChanged ( fileName , eventKind , path ) ,
1931
- PollingInterval . Medium ,
1932
- info . path ,
1933
- WatchType . ClosedScriptInfo
1934
- ) ;
1936
+ const indexOfNodeModules = info . path . indexOf ( "/node_modules/" ) ;
1937
+ if ( ! this . host . getModifiedTime || indexOfNodeModules === - 1 ) {
1938
+ info . fileWatcher = this . watchFactory . watchFilePath (
1939
+ this . host ,
1940
+ info . fileName ,
1941
+ ( fileName , eventKind , path ) => this . onSourceFileChanged ( fileName , eventKind , path ) ,
1942
+ PollingInterval . Medium ,
1943
+ info . path ,
1944
+ WatchType . ClosedScriptInfo
1945
+ ) ;
1946
+ }
1947
+ else {
1948
+ info . mTime = this . getModifiedTime ( info ) ;
1949
+ info . fileWatcher = this . watchClosedScriptInfoInNodeModules ( info . path . substr ( 0 , indexOfNodeModules ) as Path ) ;
1950
+ }
1951
+ }
1952
+ }
1953
+
1954
+ private watchClosedScriptInfoInNodeModules ( dir : Path ) : ScriptInfoInNodeModulesWatcher {
1955
+ // Watch only directory
1956
+ const existing = this . scriptInfoInNodeModulesWatchers . get ( dir ) ;
1957
+ if ( existing ) {
1958
+ existing . refCount ++ ;
1959
+ return existing ;
1960
+ }
1961
+
1962
+ const watchDir = dir + "/node_modules" as Path ;
1963
+ const watcher = this . watchFactory . watchDirectory (
1964
+ this . host ,
1965
+ watchDir ,
1966
+ ( fileOrDirectory ) => {
1967
+ const fileOrDirectoryPath = this . toPath ( fileOrDirectory ) ;
1968
+ // Has extension
1969
+ Debug . assert ( result . refCount > 0 ) ;
1970
+ if ( watchDir === fileOrDirectoryPath ) {
1971
+ this . refreshScriptInfosInDirectory ( watchDir ) ;
1972
+ }
1973
+ else {
1974
+ const info = this . getScriptInfoForPath ( fileOrDirectoryPath ) ;
1975
+ if ( info ) {
1976
+ if ( isScriptInfoWatchedFromNodeModules ( info ) ) {
1977
+ this . refreshScriptInfo ( info ) ;
1978
+ }
1979
+ }
1980
+ // Folder
1981
+ else if ( ! hasExtension ( fileOrDirectoryPath ) ) {
1982
+ this . refreshScriptInfosInDirectory ( fileOrDirectoryPath ) ;
1983
+ }
1984
+ }
1985
+ } ,
1986
+ WatchDirectoryFlags . Recursive ,
1987
+ WatchType . NodeModulesForClosedScriptInfo
1988
+ ) ;
1989
+ const result : ScriptInfoInNodeModulesWatcher = {
1990
+ close : ( ) => {
1991
+ if ( result . refCount === 1 ) {
1992
+ watcher . close ( ) ;
1993
+ this . scriptInfoInNodeModulesWatchers . delete ( dir ) ;
1994
+ }
1995
+ else {
1996
+ result . refCount -- ;
1997
+ }
1998
+ } ,
1999
+ refCount : 1
2000
+ } ;
2001
+ this . scriptInfoInNodeModulesWatchers . set ( dir , result ) ;
2002
+ return result ;
2003
+ }
2004
+
2005
+ private getModifiedTime ( info : ScriptInfo ) {
2006
+ return ( this . host . getModifiedTime ! ( info . path ) || missingFileModifiedTime ) . getTime ( ) ;
2007
+ }
2008
+
2009
+ private refreshScriptInfo ( info : ScriptInfo ) {
2010
+ const mTime = this . getModifiedTime ( info ) ;
2011
+ if ( mTime !== info . mTime ) {
2012
+ const eventKind = getFileWatcherEventKind ( info . mTime ! , mTime ) ;
2013
+ info . mTime = mTime ;
2014
+ this . onSourceFileChanged ( info . fileName , eventKind , info . path ) ;
1935
2015
}
1936
2016
}
1937
2017
2018
+ private refreshScriptInfosInDirectory ( dir : Path ) {
2019
+ dir = dir + directorySeparator as Path ;
2020
+ this . filenameToScriptInfo . forEach ( info => {
2021
+ if ( isScriptInfoWatchedFromNodeModules ( info ) && startsWith ( info . path , dir ) ) {
2022
+ this . refreshScriptInfo ( info ) ;
2023
+ }
2024
+ } ) ;
2025
+ }
2026
+
1938
2027
private stopWatchingScriptInfo ( info : ScriptInfo ) {
1939
2028
if ( info . fileWatcher ) {
1940
2029
info . fileWatcher . close ( ) ;
0 commit comments