@@ -489,8 +489,8 @@ namespace FourSlash {
489
489
return diagnostics ;
490
490
}
491
491
492
- private getRefactorDiagnostics ( fileName : string , range ?: ts . TextRange ) : ts . RefactorDiagnostic [ ] {
493
- return this . languageService . getRefactorDiagnostics ( fileName , range ) ;
492
+ private getRefactorDiagnostics ( fileName : string ) : ts . Diagnostic [ ] {
493
+ return this . languageService . getRefactorDiagnostics ( fileName ) ;
494
494
}
495
495
496
496
private getAllDiagnostics ( ) : ts . Diagnostic [ ] {
@@ -2200,20 +2200,15 @@ namespace FourSlash {
2200
2200
return actions ;
2201
2201
}
2202
2202
2203
- private getRefactorActions ( fileName : string , range ? : ts . TextRange , formattingOptions ?: ts . FormatCodeSettings ) : ts . CodeAction [ ] {
2204
- const diagnostics = this . getRefactorDiagnostics ( fileName , range ) ;
2205
- const actions : ts . CodeAction [ ] = [ ] ;
2206
- formattingOptions = formattingOptions || this . formatCodeSettings ;
2203
+ private applyAllCodeActions ( fileName : string , codeActions : ts . CodeAction [ ] ) : void {
2204
+ if ( ! codeActions ) {
2205
+ return ;
2206
+ }
2207
2207
2208
- for ( const diagnostic of diagnostics ) {
2209
- const diagnosticRange : ts . TextRange = {
2210
- pos : diagnostic . start ,
2211
- end : diagnostic . end
2212
- } ;
2213
- const newActions = this . languageService . getCodeActionsForRefactorAtPosition ( fileName , diagnosticRange , diagnostic . code , formattingOptions ) ;
2214
- actions . push . apply ( actions , newActions ) ;
2208
+ for ( const codeAction of codeActions ) {
2209
+ const fileChanges = ts . find ( codeAction . changes , change => change . fileName === fileName ) ;
2210
+ this . applyEdits ( fileChanges . fileName , fileChanges . textChanges , /*isFormattingEdit*/ false ) ;
2215
2211
}
2216
- return actions ;
2217
2212
}
2218
2213
2219
2214
private applyCodeAction ( fileName : string , actions : ts . CodeAction [ ] , index ?: number ) : void {
@@ -2574,42 +2569,79 @@ namespace FourSlash {
2574
2569
}
2575
2570
}
2576
2571
2577
- public verifyRefactorAvailable ( negative : boolean ) {
2578
- // The ranges are used only when the refactors require a range as input information. For example the "extractMethod" refactor
2579
- // onlye one range is allowed per test
2580
- const ranges = this . getRanges ( ) ;
2581
- if ( ranges . length > 1 ) {
2582
- throw new Error ( "only one refactor range is allowed per test" ) ;
2572
+ public verifyRefactorDiagnosticsAvailableAtMarker ( negative : boolean , markerName : string , diagnosticCode ?: number ) {
2573
+ const marker = this . getMarkerByName ( markerName ) ;
2574
+ const markerPos = marker . position ;
2575
+ let foundDiagnostic = false ;
2576
+
2577
+ const refactorDiagnostics = this . getRefactorDiagnostics ( this . activeFile . fileName ) ;
2578
+ for ( const diag of refactorDiagnostics ) {
2579
+ if ( diag . start <= markerPos && diag . start + diag . length >= markerPos ) {
2580
+ foundDiagnostic = diagnosticCode === undefined || diagnosticCode === diag . code ;
2581
+ }
2582
+ }
2583
+
2584
+ if ( negative && foundDiagnostic ) {
2585
+ this . raiseError ( `verifyRefactorDiagnosticsAvailableAtMarker failed - expected no refactor diagnostic at marker ${ markerName } but found some.` ) ;
2583
2586
}
2587
+ if ( ! negative && ! foundDiagnostic ) {
2588
+ this . raiseError ( `verifyRefactorDiagnosticsAvailableAtMarker failed - expected a refactor diagnostic at marker ${ markerName } but found none.` ) ;
2589
+ }
2590
+ }
2584
2591
2585
- const range = ranges [ 0 ] ? { pos : ranges [ 0 ] . start , end : ranges [ 0 ] . end } : undefined ;
2586
- const refactorDiagnostics = this . getRefactorDiagnostics ( this . activeFile . fileName , range ) ;
2587
- if ( negative && refactorDiagnostics . length > 0 ) {
2588
- this . raiseError ( `verifyRefactorAvailable failed - expected no refactors but found some.` ) ;
2592
+ public verifyApplicableRefactorAvailableAtMarker ( negative : boolean , markerName : string ) {
2593
+ const marker = this . getMarkerByName ( markerName ) ;
2594
+ const applicableRefactors = this . languageService . getApplicableRefactors ( this . activeFile . fileName , marker . position ) ;
2595
+ const isAvailable = applicableRefactors && applicableRefactors . length > 0 ;
2596
+ if ( negative && isAvailable ) {
2597
+ this . raiseError ( `verifyApplicableRefactorAvailableAtMarker failed - expected no refactor at marker ${ markerName } but found some.` ) ;
2589
2598
}
2590
- if ( ! negative && refactorDiagnostics . length === 0 ) {
2591
- this . raiseError ( `verifyRefactorAvailable failed: expected refactor diagnostics but none found.` ) ;
2599
+ if ( ! negative && ! isAvailable ) {
2600
+ this . raiseError ( `verifyApplicableRefactorAvailableAtMarker failed - expected a refactor at marker ${ markerName } but found none .` ) ;
2592
2601
}
2593
2602
}
2594
2603
2595
- public verifyFileAfterApplyingRefactors ( expectedContent : string , formattingOptions ?: ts . FormatCodeSettings ) {
2604
+ public verifyApplicableRefactorAvailableForRange ( negative : boolean ) {
2596
2605
const ranges = this . getRanges ( ) ;
2597
- if ( ranges . length > 1 ) {
2598
- throw new Error ( "only one refactor range is allowed per test" ) ;
2599
- }
2600
-
2601
- const range = ranges [ 0 ] ? { pos : ranges [ 0 ] . start , end : ranges [ 0 ] . end } : undefined ;
2602
- const actions = this . getRefactorActions ( this . activeFile . fileName , range , formattingOptions ) ;
2603
-
2604
- // Each refactor diagnostic will return one code action, but multiple refactor diagnostics can point to the same
2605
- // code action. For example in the case of "convert function to es6 class":
2606
- //
2607
- // function foo() { }
2608
- // ^^^
2609
- // foo.prototype.getName = function () { return "name"; };
2610
- // ^^^
2611
- // These two diagnostics result in the same code action, so we only apply the first one.
2612
- this . applyCodeAction ( this . activeFile . fileName , actions , /*index*/ 0 ) ;
2606
+ if ( ! ( ranges && ranges . length === 1 ) ) {
2607
+ throw new Error ( "Exactly one refactor range is allowed per test." ) ;
2608
+ }
2609
+
2610
+ const applicableRefactors = this . languageService . getApplicableRefactors ( this . activeFile . fileName , { pos : ranges [ 0 ] . start , end : ranges [ 0 ] . end } ) ;
2611
+ const isAvailable = applicableRefactors && applicableRefactors . length > 0 ;
2612
+ if ( negative && isAvailable ) {
2613
+ this . raiseError ( `verifyApplicableRefactorAvailableForRange failed - expected no refactor but found some.` ) ;
2614
+ }
2615
+ if ( ! negative && ! isAvailable ) {
2616
+ this . raiseError ( `verifyApplicableRefactorAvailableForRange failed - expected a refactor but found none.` ) ;
2617
+ }
2618
+ }
2619
+
2620
+ public verifyFileAfterApplyingRefactorAtMarker (
2621
+ markerName : string ,
2622
+ expectedContent : string ,
2623
+ refactorKindToApply ?: ts . RefactorKind ,
2624
+ formattingOptions ?: ts . FormatCodeSettings ) {
2625
+
2626
+ formattingOptions = formattingOptions || this . formatCodeSettings ;
2627
+ const markerPos = this . getMarkerByName ( markerName ) . position ;
2628
+ const diagnostics = this . getRefactorDiagnostics ( this . activeFile . fileName ) ;
2629
+
2630
+ const diagnosticCodesAtMarker : number [ ] = [ ] ;
2631
+ for ( const diagnostic of diagnostics ) {
2632
+ if ( diagnostic . start <= markerPos && diagnostic . start + diagnostic . length >= markerPos ) {
2633
+ diagnosticCodesAtMarker . push ( diagnostic . code ) ;
2634
+ }
2635
+ }
2636
+
2637
+ const applicableRefactors = this . languageService . getApplicableRefactors ( this . activeFile . fileName , markerPos ) ;
2638
+ const applicableRefactorKinds =
2639
+ ts . map ( ts . filter ( applicableRefactors , ar => refactorKindToApply === undefined || refactorKindToApply === ar . refactorKind ) ,
2640
+ refactorInfo => refactorInfo . refactorKind ) ;
2641
+ const codeActions = this . languageService . getRefactorCodeActions (
2642
+ this . activeFile . fileName , formattingOptions , markerPos , applicableRefactorKinds , diagnosticCodesAtMarker ) ;
2643
+
2644
+ this . applyAllCodeActions ( this . activeFile . fileName , codeActions ) ;
2613
2645
const actualContent = this . getFileContent ( this . activeFile . fileName ) ;
2614
2646
2615
2647
if ( this . normalizeNewlines ( actualContent ) !== this . normalizeNewlines ( expectedContent ) ) {
@@ -3409,8 +3441,16 @@ namespace FourSlashInterface {
3409
3441
this . state . verifyCodeFixAvailable ( this . negative ) ;
3410
3442
}
3411
3443
3412
- public refactorAvailable ( ) {
3413
- this . state . verifyRefactorAvailable ( this . negative ) ;
3444
+ public refactorDiagnosticsAvailableAtMarker ( markerName : string , refactorCode ?: number ) {
3445
+ this . state . verifyRefactorDiagnosticsAvailableAtMarker ( this . negative , markerName , refactorCode ) ;
3446
+ }
3447
+
3448
+ public applicableRefactorAvailableAtMarker ( markerName : string ) {
3449
+ this . state . verifyApplicableRefactorAvailableAtMarker ( this . negative , markerName ) ;
3450
+ }
3451
+
3452
+ public applicableRefactorAvailableForRange ( ) {
3453
+ this . state . verifyApplicableRefactorAvailableForRange ( this . negative ) ;
3414
3454
}
3415
3455
}
3416
3456
@@ -3618,8 +3658,8 @@ namespace FourSlashInterface {
3618
3658
this . state . verifyRangeAfterCodeFix ( expectedText , includeWhiteSpace , errorCode , index ) ;
3619
3659
}
3620
3660
3621
- public fileAfterApplyingRefactors ( expectedContent : string , formattingOptions : ts . FormatCodeSettings ) : void {
3622
- this . state . verifyFileAfterApplyingRefactors ( expectedContent , formattingOptions ) ;
3661
+ public fileAfterApplyingRefactorsAtMarker ( markerName : string , expectedContent : string , refactorKindToApply ?: ts . RefactorKind , formattingOptions ? : ts . FormatCodeSettings ) : void {
3662
+ this . state . verifyFileAfterApplyingRefactorAtMarker ( markerName , expectedContent , refactorKindToApply , formattingOptions ) ;
3623
3663
}
3624
3664
3625
3665
public importFixAtPosition ( expectedTextArray : string [ ] , errorCode ?: number ) : void {
0 commit comments