@@ -33,6 +33,8 @@ import {
3333 emptyOptions ,
3434 endsWith ,
3535 ensureTrailingDirectorySeparator ,
36+ equateStringsCaseInsensitive ,
37+ equateStringsCaseSensitive ,
3638 ExtendedConfigCacheEntry ,
3739 FileExtensionInfo ,
3840 fileExtensionIs ,
@@ -1060,7 +1062,7 @@ export interface ParsedConfig {
10601062 */
10611063 projects : Map < NormalizedPath , boolean > ;
10621064 parsedCommandLine ?: ParsedCommandLine ;
1063- watchedDirectories ?: Map < string , WildcardDirectoryWatcher > ;
1065+ watchedDirectories ?: Map < string , WildcardDirectoryWatcher < WildcardWatcher > > ;
10641066 /**
10651067 * true if watchedDirectories need to be updated as per parsedCommandLine's updated watched directories
10661068 */
@@ -1852,80 +1854,22 @@ export class ProjectService {
18521854 /**
18531855 * This is to watch whenever files are added or removed to the wildcard directories
18541856 */
1855- private watchWildcardDirectory ( directory : string , flags : WatchDirectoryFlags , configFileName : NormalizedPath , config : ParsedConfig ) {
1857+ private watchWildcardDirectory (
1858+ directory : string ,
1859+ flags : WatchDirectoryFlags ,
1860+ configFileName : NormalizedPath ,
1861+ config : ParsedConfig ,
1862+ ) {
18561863 let watcher : FileWatcher | undefined = this . watchFactory . watchDirectory (
18571864 directory ,
1858- fileOrDirectory => {
1859- const fileOrDirectoryPath = this . toPath ( fileOrDirectory ) ;
1860- const fsResult = config . cachedDirectoryStructureHost . addOrDeleteFileOrDirectory ( fileOrDirectory , fileOrDirectoryPath ) ;
1861- if (
1862- getBaseFileName ( fileOrDirectoryPath ) === "package.json" && ! isInsideNodeModules ( fileOrDirectoryPath ) &&
1863- ( fsResult && fsResult . fileExists || ! fsResult && this . host . fileExists ( fileOrDirectory ) )
1864- ) {
1865- const file = this . getNormalizedAbsolutePath ( fileOrDirectory ) ;
1866- this . logger . info ( `Config: ${ configFileName } Detected new package.json: ${ file } ` ) ;
1867- this . packageJsonCache . addOrUpdate ( file , fileOrDirectoryPath ) ;
1868- this . watchPackageJsonFile ( file , fileOrDirectoryPath , result ) ;
1869- }
1870-
1871- const configuredProjectForConfig = this . findConfiguredProjectByProjectName ( configFileName ) ;
1872- if (
1873- isIgnoredFileFromWildCardWatching ( {
1874- watchedDirPath : this . toPath ( directory ) ,
1875- fileOrDirectory,
1876- fileOrDirectoryPath,
1877- configFileName,
1878- extraFileExtensions : this . hostConfiguration . extraFileExtensions ,
1879- currentDirectory : this . currentDirectory ,
1880- options : config . parsedCommandLine ! . options ,
1881- program : configuredProjectForConfig ?. getCurrentProgram ( ) || config . parsedCommandLine ! . fileNames ,
1882- useCaseSensitiveFileNames : this . host . useCaseSensitiveFileNames ,
1883- writeLog : s => this . logger . info ( s ) ,
1884- toPath : s => this . toPath ( s ) ,
1885- getScriptKind : configuredProjectForConfig ? ( fileName => configuredProjectForConfig . getScriptKind ( fileName ) ) : undefined ,
1886- } )
1887- ) return ;
1888-
1889- // Reload is pending, do the reload
1890- if ( config . updateLevel !== ProgramUpdateLevel . Full ) config . updateLevel = ProgramUpdateLevel . RootNamesAndUpdate ;
1891- config . projects . forEach ( ( watchWildcardDirectories , projectCanonicalPath ) => {
1892- if ( ! watchWildcardDirectories ) return ;
1893- const project = this . getConfiguredProjectByCanonicalConfigFilePath ( projectCanonicalPath ) ;
1894- if ( ! project ) return ;
1895-
1896- if (
1897- configuredProjectForConfig !== project &&
1898- this . getHostPreferences ( ) . includeCompletionsForModuleExports
1899- ) {
1900- const path = this . toPath ( configFileName ) ;
1901- if ( find ( project . getCurrentProgram ( ) ?. getResolvedProjectReferences ( ) , ref => ref ?. sourceFile . path === path ) ) {
1902- project . markAutoImportProviderAsDirty ( ) ;
1903- }
1904- }
1905-
1906- // Load root file names for configured project with the config file name
1907- // But only schedule update if project references this config file
1908- const updateLevel = configuredProjectForConfig === project ? ProgramUpdateLevel . RootNamesAndUpdate : ProgramUpdateLevel . Update ;
1909- if ( project . pendingUpdateLevel > updateLevel ) return ;
1910-
1911- // don't trigger callback on open, existing files
1912- if ( this . openFiles . has ( fileOrDirectoryPath ) ) {
1913- const info = Debug . checkDefined ( this . getScriptInfoForPath ( fileOrDirectoryPath ) ) ;
1914- if ( info . isAttached ( project ) ) {
1915- const loadLevelToSet = Math . max ( updateLevel , project . openFileWatchTriggered . get ( fileOrDirectoryPath ) || ProgramUpdateLevel . Update ) as ProgramUpdateLevel ;
1916- project . openFileWatchTriggered . set ( fileOrDirectoryPath , loadLevelToSet ) ;
1917- }
1918- else {
1919- project . pendingUpdateLevel = updateLevel ;
1920- this . delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles ( project ) ;
1921- }
1922- }
1923- else {
1924- project . pendingUpdateLevel = updateLevel ;
1925- this . delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles ( project ) ;
1926- }
1927- } ) ;
1928- } ,
1865+ fileOrDirectory =>
1866+ this . onWildCardDirectoryWatcherInvoke (
1867+ directory ,
1868+ configFileName ,
1869+ config ,
1870+ result ,
1871+ fileOrDirectory ,
1872+ ) ,
19291873 flags ,
19301874 this . getWatchOptionsFromProjectWatchOptions ( config . parsedCommandLine ! . watchOptions , getDirectoryPath ( configFileName ) ) ,
19311875 WatchType . WildcardDirectory ,
@@ -1949,6 +1893,84 @@ export class ProjectService {
19491893 return result ;
19501894 }
19511895
1896+ private onWildCardDirectoryWatcherInvoke (
1897+ directory : string ,
1898+ configFileName : NormalizedPath ,
1899+ config : ParsedConfig ,
1900+ wildCardWatcher : WildcardWatcher ,
1901+ fileOrDirectory : string ,
1902+ ) {
1903+ const fileOrDirectoryPath = this . toPath ( fileOrDirectory ) ;
1904+ const fsResult = config . cachedDirectoryStructureHost . addOrDeleteFileOrDirectory ( fileOrDirectory , fileOrDirectoryPath ) ;
1905+ if (
1906+ getBaseFileName ( fileOrDirectoryPath ) === "package.json" && ! isInsideNodeModules ( fileOrDirectoryPath ) &&
1907+ ( fsResult && fsResult . fileExists || ! fsResult && this . host . fileExists ( fileOrDirectory ) )
1908+ ) {
1909+ const file = this . getNormalizedAbsolutePath ( fileOrDirectory ) ;
1910+ this . logger . info ( `Config: ${ configFileName } Detected new package.json: ${ file } ` ) ;
1911+ this . packageJsonCache . addOrUpdate ( file , fileOrDirectoryPath ) ;
1912+ this . watchPackageJsonFile ( file , fileOrDirectoryPath , wildCardWatcher ) ;
1913+ }
1914+
1915+ const configuredProjectForConfig = this . findConfiguredProjectByProjectName ( configFileName ) ;
1916+ if (
1917+ isIgnoredFileFromWildCardWatching ( {
1918+ watchedDirPath : this . toPath ( directory ) ,
1919+ fileOrDirectory,
1920+ fileOrDirectoryPath,
1921+ configFileName,
1922+ extraFileExtensions : this . hostConfiguration . extraFileExtensions ,
1923+ currentDirectory : this . currentDirectory ,
1924+ options : config . parsedCommandLine ! . options ,
1925+ program : configuredProjectForConfig ?. getCurrentProgram ( ) || config . parsedCommandLine ! . fileNames ,
1926+ useCaseSensitiveFileNames : this . host . useCaseSensitiveFileNames ,
1927+ writeLog : s => this . logger . info ( s ) ,
1928+ toPath : s => this . toPath ( s ) ,
1929+ getScriptKind : configuredProjectForConfig ? ( fileName => configuredProjectForConfig . getScriptKind ( fileName ) ) : undefined ,
1930+ } )
1931+ ) return ;
1932+
1933+ // Reload is pending, do the reload
1934+ if ( config . updateLevel !== ProgramUpdateLevel . Full ) config . updateLevel = ProgramUpdateLevel . RootNamesAndUpdate ;
1935+ config . projects . forEach ( ( watchWildcardDirectories , projectCanonicalPath ) => {
1936+ if ( ! watchWildcardDirectories ) return ;
1937+ const project = this . getConfiguredProjectByCanonicalConfigFilePath ( projectCanonicalPath ) ;
1938+ if ( ! project ) return ;
1939+
1940+ if (
1941+ configuredProjectForConfig !== project &&
1942+ this . getHostPreferences ( ) . includeCompletionsForModuleExports
1943+ ) {
1944+ const path = this . toPath ( configFileName ) ;
1945+ if ( find ( project . getCurrentProgram ( ) ?. getResolvedProjectReferences ( ) , ref => ref ?. sourceFile . path === path ) ) {
1946+ project . markAutoImportProviderAsDirty ( ) ;
1947+ }
1948+ }
1949+
1950+ // Load root file names for configured project with the config file name
1951+ // But only schedule update if project references this config file
1952+ const updateLevel = configuredProjectForConfig === project ? ProgramUpdateLevel . RootNamesAndUpdate : ProgramUpdateLevel . Update ;
1953+ if ( project . pendingUpdateLevel > updateLevel ) return ;
1954+
1955+ // don't trigger callback on open, existing files
1956+ if ( this . openFiles . has ( fileOrDirectoryPath ) ) {
1957+ const info = Debug . checkDefined ( this . getScriptInfoForPath ( fileOrDirectoryPath ) ) ;
1958+ if ( info . isAttached ( project ) ) {
1959+ const loadLevelToSet = Math . max ( updateLevel , project . openFileWatchTriggered . get ( fileOrDirectoryPath ) || ProgramUpdateLevel . Update ) as ProgramUpdateLevel ;
1960+ project . openFileWatchTriggered . set ( fileOrDirectoryPath , loadLevelToSet ) ;
1961+ }
1962+ else {
1963+ project . pendingUpdateLevel = updateLevel ;
1964+ this . delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles ( project ) ;
1965+ }
1966+ }
1967+ else {
1968+ project . pendingUpdateLevel = updateLevel ;
1969+ this . delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles ( project ) ;
1970+ }
1971+ } ) ;
1972+ }
1973+
19521974 private delayUpdateProjectsFromParsedConfigOnConfigFileChange ( canonicalConfigFilePath : NormalizedPath , loadReason : string ) {
19531975 const configFileExistenceInfo = this . configFileExistenceInfoCache . get ( canonicalConfigFilePath ) ;
19541976 if ( ! configFileExistenceInfo ?. config ) return false ;
@@ -4434,8 +4456,42 @@ export class ProjectService {
44344456 this . removeOrphanScriptInfos ( ) ;
44354457 }
44364458
4459+ private tryInvokeWildCardDirectories ( info : ScriptInfo ) {
4460+ // This might not have reflected in projects,
4461+ this . configFileExistenceInfoCache . forEach ( ( configFileExistenceInfo , config ) => {
4462+ if (
4463+ ! configFileExistenceInfo . config ?. parsedCommandLine ||
4464+ contains (
4465+ configFileExistenceInfo . config . parsedCommandLine . fileNames ,
4466+ info . fileName ,
4467+ ! this . host . useCaseSensitiveFileNames ? equateStringsCaseInsensitive : equateStringsCaseSensitive ,
4468+ )
4469+ ) {
4470+ return ;
4471+ }
4472+ configFileExistenceInfo . config . watchedDirectories ?. forEach ( ( watcher , directory ) => {
4473+ if ( containsPath ( directory , info . fileName , ! this . host . useCaseSensitiveFileNames ) ) {
4474+ this . logger . info ( `Invoking ${ config } :: wildcard for open scriptInfo:: ${ info . fileName } ` ) ;
4475+ this . onWildCardDirectoryWatcherInvoke (
4476+ directory ,
4477+ config ,
4478+ configFileExistenceInfo . config ! ,
4479+ watcher . watcher ,
4480+ info . fileName ,
4481+ ) ;
4482+ }
4483+ } ) ;
4484+ } ) ;
4485+ }
4486+
44374487 openClientFileWithNormalizedPath ( fileName : NormalizedPath , fileContent ?: string , scriptKind ?: ScriptKind , hasMixedContent ?: boolean , projectRootPath ?: NormalizedPath ) : OpenConfiguredProjectResult {
4488+ const existing = this . getScriptInfoForPath ( normalizedPathToPath (
4489+ fileName ,
4490+ projectRootPath ? this . getNormalizedAbsolutePath ( projectRootPath ) : this . currentDirectory ,
4491+ this . toCanonicalFileName ,
4492+ ) ) ;
44384493 const info = this . getOrCreateOpenScriptInfo ( fileName , fileContent , scriptKind , hasMixedContent , projectRootPath ) ;
4494+ if ( ! existing && info && ! info . isDynamic ) this . tryInvokeWildCardDirectories ( info ) ;
44394495 const { retainProjects, ...result } = this . assignProjectToOpenedScriptInfo ( info ) ;
44404496 this . cleanupProjectsAndScriptInfos (
44414497 retainProjects ,
@@ -4636,10 +4692,16 @@ export class ProjectService {
46364692
46374693 /** @internal */
46384694 applyChangesInOpenFiles ( openFiles : Iterable < OpenFileArguments > | undefined , changedFiles ?: Iterable < ChangeFileArguments > , closedFiles ?: string [ ] ) : void {
4695+ let existingOpenScriptInfos : ( ScriptInfo | undefined ) [ ] | undefined ;
46394696 let openScriptInfos : ScriptInfo [ ] | undefined ;
46404697 let assignOrphanScriptInfosToInferredProject = false ;
46414698 if ( openFiles ) {
46424699 for ( const file of openFiles ) {
4700+ ( existingOpenScriptInfos ??= [ ] ) . push ( this . getScriptInfoForPath ( normalizedPathToPath (
4701+ toNormalizedPath ( file . fileName ) ,
4702+ file . projectRootPath ? this . getNormalizedAbsolutePath ( file . projectRootPath ) : this . currentDirectory ,
4703+ this . toCanonicalFileName ,
4704+ ) ) ) ;
46434705 // Create script infos so we have the new content for all the open files before we do any updates to projects
46444706 const info = this . getOrCreateOpenScriptInfo (
46454707 toNormalizedPath ( file . fileName ) ,
@@ -4670,6 +4732,13 @@ export class ProjectService {
46704732
46714733 // All the script infos now exist, so ok to go update projects for open files
46724734 let retainProjects : Set < ConfiguredProject > | undefined ;
4735+ forEach (
4736+ existingOpenScriptInfos ,
4737+ ( existing , index ) =>
4738+ ! existing && openScriptInfos ! [ index ] && ! openScriptInfos ! [ index ] . isDynamic ?
4739+ this . tryInvokeWildCardDirectories ( openScriptInfos ! [ index ] ) :
4740+ undefined ,
4741+ ) ;
46734742 openScriptInfos ?. forEach ( info => this . assignProjectToOpenedScriptInfo ( info ) . retainProjects ?. forEach ( p => ( retainProjects ??= new Set ( ) ) . add ( p ) ) ) ;
46744743
46754744 // While closing files there could be open files that needed assigning new inferred projects, do it now
0 commit comments