@@ -62,6 +62,18 @@ namespace ts.projectSystem {
6262 getLogFileName : ( ) => undefined ,
6363 } ;
6464
65+ function createHasErrorMessageLogger ( ) {
66+ let hasErrorMsg = false ;
67+ const { close, hasLevel, loggingEnabled, startGroup, endGroup, info, getLogFileName, perftrc } = nullLogger ;
68+ const logger : server . Logger = {
69+ close, hasLevel, loggingEnabled, startGroup, endGroup, info, getLogFileName, perftrc,
70+ msg : ( ) => {
71+ hasErrorMsg = true ;
72+ }
73+ } ;
74+ return { logger, hasErrorMsg : ( ) => hasErrorMsg } ;
75+ }
76+
6577 export class TestTypingsInstaller extends TI . TypingsInstaller implements server . ITypingsInstaller {
6678 protected projectService : server . ProjectService ;
6779 constructor (
@@ -2917,14 +2929,7 @@ namespace ts.projectSystem {
29172929 } ) ;
29182930
29192931 it ( "Getting errors from closed script info does not throw exception (because of getting project from orphan script info)" , ( ) => {
2920- let hasErrorMsg = false ;
2921- const { close, hasLevel, loggingEnabled, startGroup, endGroup, info, getLogFileName, perftrc } = nullLogger ;
2922- const logger : server . Logger = {
2923- close, hasLevel, loggingEnabled, startGroup, endGroup, info, getLogFileName, perftrc,
2924- msg : ( ) => {
2925- hasErrorMsg = true ;
2926- }
2927- } ;
2932+ const { logger, hasErrorMsg } = createHasErrorMessageLogger ( ) ;
29282933 const f1 = {
29292934 path : "/a/b/app.ts" ,
29302935 content : "let x = 1;"
@@ -2954,7 +2959,7 @@ namespace ts.projectSystem {
29542959 files : [ f1 . path ]
29552960 }
29562961 } ) ;
2957- assert . isFalse ( hasErrorMsg ) ;
2962+ assert . isFalse ( hasErrorMsg ( ) ) ;
29582963 } ) ;
29592964
29602965 it ( "Changed module resolution reflected when specifying files list" , ( ) => {
@@ -3320,6 +3325,129 @@ namespace ts.projectSystem {
33203325 assert . equal ( info . containingProjects . length , 0 ) ;
33213326 }
33223327 } ) ;
3328+
3329+ it ( "handles delayed directory watch invoke on file creation" , ( ) => {
3330+ const projectRootPath = "/users/username/projects/project" ;
3331+ const fileB : File = {
3332+ path : `${ projectRootPath } /b.ts` ,
3333+ content : "export const b = 10;"
3334+ } ;
3335+ const fileA : File = {
3336+ path : `${ projectRootPath } /a.ts` ,
3337+ content : "export const a = 10;"
3338+ } ;
3339+ const fileSubA : File = {
3340+ path : `${ projectRootPath } /sub/a.ts` ,
3341+ content : fileA . content
3342+ } ;
3343+ const config : File = {
3344+ path : `${ projectRootPath } /tsconfig.json` ,
3345+ content : "{}"
3346+ } ;
3347+ const files = [ fileSubA , fileB , config , libFile ] ;
3348+ const host = createServerHost ( files ) ;
3349+ const { logger, hasErrorMsg } = createHasErrorMessageLogger ( ) ;
3350+ const session = createSession ( host , { canUseEvents : true , noGetErrOnBackgroundUpdate : true , logger } ) ;
3351+ openFile ( fileB ) ;
3352+ openFile ( fileSubA ) ;
3353+
3354+ const services = session . getProjectService ( ) ;
3355+ checkNumberOfProjects ( services , { configuredProjects : 1 } ) ;
3356+ checkProjectActualFiles ( services . configuredProjects . get ( config . path ) ! , files . map ( f => f . path ) ) ;
3357+ host . checkTimeoutQueueLengthAndRun ( 0 ) ;
3358+
3359+ // This should schedule 2 timeouts for ensuring project structure and ensuring projects for open file
3360+ const filesWithFileA = files . map ( f => f === fileSubA ? fileA : f ) ;
3361+ host . reloadFS ( files . map ( f => f === fileSubA ? fileA : f ) ) ;
3362+ host . checkTimeoutQueueLength ( 2 ) ;
3363+
3364+ closeFile ( fileSubA ) ;
3365+ // This should cancel existing updates and schedule new ones
3366+ host . checkTimeoutQueueLength ( 2 ) ;
3367+ checkNumberOfProjects ( services , { configuredProjects : 1 } ) ;
3368+ checkProjectActualFiles ( services . configuredProjects . get ( config . path ) ! , files . map ( f => f . path ) ) ;
3369+
3370+ // Open the fileA (as if rename)
3371+ openFile ( fileA ) ;
3372+
3373+ // config project is updated to check if fileA is present in it
3374+ checkNumberOfProjects ( services , { configuredProjects : 1 } ) ;
3375+ checkProjectActualFiles ( services . configuredProjects . get ( config . path ) ! , filesWithFileA . map ( f => f . path ) ) ;
3376+
3377+ // Run the timeout for updating configured project and ensuring projects for open file
3378+ host . checkTimeoutQueueLengthAndRun ( 2 ) ;
3379+ checkProjectActualFiles ( services . configuredProjects . get ( config . path ) ! , filesWithFileA . map ( f => f . path ) ) ;
3380+
3381+ // file is deleted but watches are not yet invoked
3382+ const originalFileExists = host . fileExists ;
3383+ host . fileExists = s => s === fileA . path ? false : originalFileExists . call ( host , s ) ;
3384+ closeFile ( fileA ) ;
3385+ host . checkTimeoutQueueLength ( 2 ) ; // Update configured project and projects for open file
3386+ checkProjectActualFiles ( services . configuredProjects . get ( config . path ) ! , filesWithFileA . map ( f => f . path ) ) ;
3387+
3388+ // host.fileExists = originalFileExists;
3389+ openFile ( fileSubA ) ;
3390+ // This should create inferred project since fileSubA not on the disk
3391+ checkProjectActualFiles ( services . configuredProjects . get ( config . path ) ! , mapDefined ( filesWithFileA , f => f === fileA ? undefined : f . path ) ) ;
3392+ checkProjectActualFiles ( services . inferredProjects [ 0 ] , [ fileSubA . path , libFile . path ] ) ;
3393+
3394+ host . checkTimeoutQueueLengthAndRun ( 2 ) ; // Update configured project and projects for open file
3395+ host . fileExists = originalFileExists ;
3396+
3397+ // Actually trigger the file move
3398+ host . reloadFS ( files ) ;
3399+ host . checkTimeoutQueueLength ( 2 ) ;
3400+ const fileBErrorTimeoutId = host . getNextTimeoutId ( ) ;
3401+
3402+ session . executeCommandSeq < protocol . GeterrRequest > ( {
3403+ command : protocol . CommandTypes . Geterr ,
3404+ arguments : {
3405+ files : [ fileB . path , fileSubA . path ] ,
3406+ delay : 0
3407+ }
3408+ } ) ;
3409+ const getErrSeqId = session . getSeq ( ) ;
3410+ host . checkTimeoutQueueLength ( 3 ) ;
3411+
3412+ session . clearMessages ( ) ;
3413+ host . runQueuedTimeoutCallbacks ( fileBErrorTimeoutId ) ;
3414+ checkErrorMessage ( session , "syntaxDiag" , { file : fileB . path , diagnostics : [ ] } ) ;
3415+
3416+ session . clearMessages ( ) ;
3417+ host . runQueuedImmediateCallbacks ( ) ;
3418+ checkErrorMessage ( session , "semanticDiag" , { file : fileB . path , diagnostics : [ ] } ) ;
3419+
3420+ session . clearMessages ( ) ;
3421+ const fileSubAErrorTimeoutId = host . getNextTimeoutId ( ) ;
3422+ host . runQueuedImmediateCallbacks ( ) ;
3423+ checkErrorMessage ( session , "suggestionDiag" , { file : fileB . path , diagnostics : [ ] } ) ;
3424+
3425+ session . clearMessages ( ) ;
3426+ host . checkTimeoutQueueLength ( 3 ) ;
3427+ host . runQueuedTimeoutCallbacks ( fileSubAErrorTimeoutId ) ;
3428+ checkCompleteEvent ( session , 1 , getErrSeqId ) ;
3429+ assert . isFalse ( hasErrorMsg ( ) ) ;
3430+
3431+ function openFile ( file : File ) {
3432+ session . executeCommandSeq < protocol . OpenRequest > ( {
3433+ command : protocol . CommandTypes . Open ,
3434+ arguments : {
3435+ file : file . path ,
3436+ fileContent : file . content ,
3437+ projectRootPath
3438+ }
3439+ } ) ;
3440+ }
3441+
3442+ function closeFile ( file : File ) {
3443+ session . executeCommandSeq < protocol . CloseRequest > ( {
3444+ command : protocol . CommandTypes . Close ,
3445+ arguments : {
3446+ file : file . path
3447+ }
3448+ } ) ;
3449+ }
3450+ } ) ;
33233451 } ) ;
33243452
33253453 describe ( "tsserverProjectSystem Proper errors" , ( ) => {
0 commit comments