@@ -34,10 +34,6 @@ namespace ts.projectSystem {
3434 getLogFileName : ( ) : string => undefined
3535 } ;
3636
37- export const nullCancellationToken : HostCancellationToken = {
38- isCancellationRequested : ( ) => false
39- } ;
40-
4137 export const { content : libFileContent } = Harness . getDefaultLibraryFile ( Harness . IO ) ;
4238 export const libFile : FileOrFolder = {
4339 path : "/a/lib/lib.d.ts" ,
@@ -158,17 +154,33 @@ namespace ts.projectSystem {
158154 }
159155
160156 class TestSession extends server . Session {
157+ private seq = 0 ;
158+
161159 getProjectService ( ) {
162160 return this . projectService ;
163161 }
162+
163+ public getSeq ( ) {
164+ return this . seq ;
165+ }
166+
167+ public getNextSeq ( ) {
168+ return this . seq + 1 ;
169+ }
170+
171+ public executeCommandSeq < T extends server . protocol . Request > ( request : Partial < T > ) {
172+ this . seq ++ ;
173+ request . seq = this . seq ;
174+ request . type = "request" ;
175+ return this . executeCommand ( < T > request ) ;
176+ }
164177 } ;
165178
166- export function createSession ( host : server . ServerHost , typingsInstaller ?: server . ITypingsInstaller , projectServiceEventHandler ?: server . ProjectServiceEventHandler ) {
179+ export function createSession ( host : server . ServerHost , typingsInstaller ?: server . ITypingsInstaller , projectServiceEventHandler ?: server . ProjectServiceEventHandler , cancellationToken ?: server . ServerCancellationToken ) {
167180 if ( typingsInstaller === undefined ) {
168181 typingsInstaller = new TestTypingsInstaller ( "/a/data/" , /*throttleLimit*/ 5 , host ) ;
169182 }
170-
171- return new TestSession ( host , nullCancellationToken , /*useSingleInferredProject*/ false , typingsInstaller , Utils . byteLength , process . hrtime , nullLogger , /*canUseEvents*/ projectServiceEventHandler !== undefined , projectServiceEventHandler ) ;
183+ return new TestSession ( host , cancellationToken || server . nullCancellationToken , /*useSingleInferredProject*/ false , typingsInstaller , Utils . byteLength , process . hrtime , nullLogger , /*canUseEvents*/ projectServiceEventHandler !== undefined , projectServiceEventHandler ) ;
172184 }
173185
174186 export interface CreateProjectServiceParameters {
@@ -191,7 +203,7 @@ namespace ts.projectSystem {
191203 }
192204 }
193205 export function createProjectService ( host : server . ServerHost , parameters : CreateProjectServiceParameters = { } ) {
194- const cancellationToken = parameters . cancellationToken || nullCancellationToken ;
206+ const cancellationToken = parameters . cancellationToken || server . nullCancellationToken ;
195207 const logger = parameters . logger || nullLogger ;
196208 const useSingleInferredProject = parameters . useSingleInferredProject !== undefined ? parameters . useSingleInferredProject : false ;
197209 return new TestProjectService ( host , logger , cancellationToken , useSingleInferredProject , parameters . typingsInstaller , parameters . eventHandler ) ;
@@ -328,6 +340,8 @@ namespace ts.projectSystem {
328340 export class TestServerHost implements server . ServerHost {
329341 args : string [ ] = [ ] ;
330342
343+ private readonly output : string [ ] = [ ] ;
344+
331345 private fs : ts . FileMap < FSEntry > ;
332346 private getCanonicalFileName : ( s : string ) => string ;
333347 private toPath : ( f : string ) => Path ;
@@ -477,6 +491,10 @@ namespace ts.projectSystem {
477491 this . timeoutCallbacks . invoke ( ) ;
478492 }
479493
494+ runQueuedImmediateCallbacks ( ) {
495+ this . immediateCallbacks . invoke ( ) ;
496+ }
497+
480498 setImmediate ( callback : TimeOutCallback , _time : number , ...args : any [ ] ) {
481499 return this . immediateCallbacks . register ( callback , args ) ;
482500 }
@@ -509,7 +527,17 @@ namespace ts.projectSystem {
509527 this . reloadFS ( filesOrFolders ) ;
510528 }
511529
512- write ( ) { }
530+ write ( message : string ) {
531+ this . output . push ( message ) ;
532+ }
533+
534+ getOutput ( ) : ReadonlyArray < string > {
535+ return this . output ;
536+ }
537+
538+ clearOutput ( ) {
539+ this . output . length = 0 ;
540+ }
513541
514542 readonly readFile = ( s : string ) => ( < File > this . fs . get ( this . toPath ( s ) ) ) . content ;
515543 readonly resolvePath = ( s : string ) => s ;
@@ -3131,6 +3159,200 @@ namespace ts.projectSystem {
31313159 } ) ;
31323160 } ) ;
31333161
3162+ describe ( "cancellationToken" , ( ) => {
3163+ it ( "is attached to request" , ( ) => {
3164+ const f1 = {
3165+ path : "/a/b/app.ts" ,
3166+ content : "let xyz = 1;"
3167+ } ;
3168+ const host = createServerHost ( [ f1 ] ) ;
3169+ let expectedRequestId : number ;
3170+ const cancellationToken : server . ServerCancellationToken = {
3171+ isCancellationRequested : ( ) => false ,
3172+ setRequest : requestId => {
3173+ if ( expectedRequestId === undefined ) {
3174+ assert . isTrue ( false , "unexpected call" )
3175+ }
3176+ assert . equal ( requestId , expectedRequestId ) ;
3177+ } ,
3178+ resetRequest : noop
3179+ }
3180+ const session = createSession ( host , /*typingsInstaller*/ undefined , /*projectServiceEventHandler*/ undefined , cancellationToken ) ;
3181+
3182+ expectedRequestId = session . getNextSeq ( ) ;
3183+ session . executeCommandSeq ( < server . protocol . OpenRequest > {
3184+ command : "open" ,
3185+ arguments : { file : f1 . path }
3186+ } ) ;
3187+
3188+ expectedRequestId = session . getNextSeq ( ) ;
3189+ session . executeCommandSeq ( < server . protocol . GeterrRequest > {
3190+ command : "geterr" ,
3191+ arguments : { files : [ f1 . path ] }
3192+ } ) ;
3193+
3194+ expectedRequestId = session . getNextSeq ( ) ;
3195+ session . executeCommandSeq ( < server . protocol . OccurrencesRequest > {
3196+ command : "occurrences" ,
3197+ arguments : { file : f1 . path , line : 1 , offset : 6 }
3198+ } ) ;
3199+
3200+ expectedRequestId = 2 ;
3201+ host . runQueuedImmediateCallbacks ( ) ;
3202+ expectedRequestId = 2 ;
3203+ host . runQueuedImmediateCallbacks ( ) ;
3204+ } ) ;
3205+
3206+ it ( "Geterr is cancellable" , ( ) => {
3207+ const f1 = {
3208+ path : "/a/app.ts" ,
3209+ content : "let x = 1"
3210+ } ;
3211+ const config = {
3212+ path : "/a/tsconfig.json" ,
3213+ content : JSON . stringify ( {
3214+ compilerOptions : { }
3215+ } )
3216+ } ;
3217+
3218+ let requestToCancel = - 1 ;
3219+ const cancellationToken : server . ServerCancellationToken = ( function ( ) {
3220+ let currentId : number ;
3221+ return < server . ServerCancellationToken > {
3222+ setRequest ( requestId ) {
3223+ currentId = requestId ;
3224+ } ,
3225+ resetRequest ( requestId ) {
3226+ assert . equal ( requestId , currentId , "unexpected request id in cancellation" )
3227+ currentId = undefined ;
3228+ } ,
3229+ isCancellationRequested ( ) {
3230+ return requestToCancel === currentId ;
3231+ }
3232+ }
3233+ } ) ( ) ;
3234+ const host = createServerHost ( [ f1 , config ] ) ;
3235+ const session = createSession ( host , /*typingsInstaller*/ undefined , ( ) => { } , cancellationToken ) ;
3236+ {
3237+ session . executeCommandSeq ( < protocol . OpenRequest > {
3238+ command : "open" ,
3239+ arguments : { file : f1 . path }
3240+ } ) ;
3241+ // send geterr for missing file
3242+ session . executeCommandSeq ( < protocol . GeterrRequest > {
3243+ command : "geterr" ,
3244+ arguments : { files : [ "/a/missing" ] }
3245+ } ) ;
3246+ // no files - expect 'completed' event
3247+ assert . equal ( host . getOutput ( ) . length , 1 , "expect 1 message" ) ;
3248+ verifyRequestCompleted ( session . getSeq ( ) , 0 ) ;
3249+ }
3250+ {
3251+ const getErrId = session . getNextSeq ( ) ;
3252+ // send geterr for a valid file
3253+ session . executeCommandSeq ( < protocol . GeterrRequest > {
3254+ command : "geterr" ,
3255+ arguments : { files : [ f1 . path ] }
3256+ } ) ;
3257+
3258+ assert . equal ( host . getOutput ( ) . length , 0 , "expect 0 messages" ) ;
3259+
3260+ // run new request
3261+ session . executeCommandSeq ( < protocol . ProjectInfoRequest > {
3262+ command : "projectInfo" ,
3263+ arguments : { file : f1 . path }
3264+ } ) ;
3265+ host . clearOutput ( ) ;
3266+
3267+ // cancel previously issued Geterr
3268+ requestToCancel = getErrId ;
3269+ host . runQueuedTimeoutCallbacks ( ) ;
3270+
3271+ assert . equal ( host . getOutput ( ) . length , 1 , "expect 1 message" ) ;
3272+ verifyRequestCompleted ( getErrId , 0 ) ;
3273+
3274+ requestToCancel = - 1 ;
3275+ }
3276+ {
3277+ const getErrId = session . getNextSeq ( ) ;
3278+ session . executeCommandSeq ( < protocol . GeterrRequest > {
3279+ command : "geterr" ,
3280+ arguments : { files : [ f1 . path ] }
3281+ } ) ;
3282+ assert . equal ( host . getOutput ( ) . length , 0 , "expect 0 messages" ) ;
3283+
3284+ // run first step
3285+ host . runQueuedTimeoutCallbacks ( ) ;
3286+ assert . equal ( host . getOutput ( ) . length , 1 , "expect 1 messages" ) ;
3287+ const e1 = < protocol . Event > getMessage ( 0 ) ;
3288+ assert . equal ( e1 . event , "syntaxDiag" ) ;
3289+ host . clearOutput ( ) ;
3290+
3291+ requestToCancel = getErrId ;
3292+ host . runQueuedImmediateCallbacks ( ) ;
3293+ assert . equal ( host . getOutput ( ) . length , 1 , "expect 1 message" ) ;
3294+ verifyRequestCompleted ( getErrId , 0 ) ;
3295+
3296+ requestToCancel = - 1 ;
3297+ }
3298+ {
3299+ const getErrId = session . getNextSeq ( ) ;
3300+ session . executeCommandSeq ( < protocol . GeterrRequest > {
3301+ command : "geterr" ,
3302+ arguments : { files : [ f1 . path ] }
3303+ } ) ;
3304+ assert . equal ( host . getOutput ( ) . length , 0 , "expect 0 messages" ) ;
3305+
3306+ // run first step
3307+ host . runQueuedTimeoutCallbacks ( ) ;
3308+ assert . equal ( host . getOutput ( ) . length , 1 , "expect 1 messages" ) ;
3309+ const e1 = < protocol . Event > getMessage ( 0 ) ;
3310+ assert . equal ( e1 . event , "syntaxDiag" ) ;
3311+ host . clearOutput ( ) ;
3312+
3313+ host . runQueuedImmediateCallbacks ( ) ;
3314+ assert . equal ( host . getOutput ( ) . length , 2 , "expect 2 messages" ) ;
3315+ const e2 = < protocol . Event > getMessage ( 0 ) ;
3316+ assert . equal ( e2 . event , "semanticDiag" ) ;
3317+ verifyRequestCompleted ( getErrId , 1 ) ;
3318+
3319+ requestToCancel = - 1 ;
3320+ }
3321+ {
3322+ const getErr1 = session . getNextSeq ( ) ;
3323+ session . executeCommandSeq ( < protocol . GeterrRequest > {
3324+ command : "geterr" ,
3325+ arguments : { files : [ f1 . path ] }
3326+ } ) ;
3327+ assert . equal ( host . getOutput ( ) . length , 0 , "expect 0 messages" ) ;
3328+ // run first step
3329+ host . runQueuedTimeoutCallbacks ( ) ;
3330+ assert . equal ( host . getOutput ( ) . length , 1 , "expect 1 messages" ) ;
3331+ const e1 = < protocol . Event > getMessage ( 0 ) ;
3332+ assert . equal ( e1 . event , "syntaxDiag" ) ;
3333+ host . clearOutput ( ) ;
3334+
3335+ session . executeCommandSeq ( < protocol . GeterrRequest > {
3336+ command : "geterr" ,
3337+ arguments : { files : [ f1 . path ] }
3338+ } ) ;
3339+ // make sure that getErr1 is completed
3340+ verifyRequestCompleted ( getErr1 , 0 ) ;
3341+ }
3342+
3343+ function verifyRequestCompleted ( expectedSeq : number , n : number ) {
3344+ const event = < protocol . RequestCompletedEvent > getMessage ( n ) ;
3345+ assert . equal ( event . event , "requestCompleted" ) ;
3346+ assert . equal ( event . body . request_seq , expectedSeq , "expectedSeq" ) ;
3347+ host . clearOutput ( ) ;
3348+ }
3349+
3350+ function getMessage ( n : number ) {
3351+ return JSON . parse ( server . extractMessage ( host . getOutput ( ) [ n ] ) ) ;
3352+ }
3353+ } ) ;
3354+ } ) ;
3355+
31343356 describe ( "maxNodeModuleJsDepth for inferred projects" , ( ) => {
31353357 it ( "should be set to 2 if the project has js root files" , ( ) => {
31363358 const file1 : FileOrFolder = {
@@ -3184,5 +3406,4 @@ namespace ts.projectSystem {
31843406 assert . isUndefined ( project . getCompilerOptions ( ) . maxNodeModuleJsDepth ) ;
31853407 } ) ;
31863408 } ) ;
3187-
31883409}
0 commit comments