@@ -489,8 +489,8 @@ namespace FourSlash {
489489 return diagnostics ;
490490 }
491491
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 ) ;
494494 }
495495
496496 private getAllDiagnostics ( ) : ts . Diagnostic [ ] {
@@ -2200,20 +2200,15 @@ namespace FourSlash {
22002200 return actions ;
22012201 }
22022202
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+ }
22072207
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 ) ;
22152211 }
2216- return actions ;
22172212 }
22182213
22192214 private applyCodeAction ( fileName : string , actions : ts . CodeAction [ ] , index ?: number ) : void {
@@ -2574,42 +2569,79 @@ namespace FourSlash {
25742569 }
25752570 }
25762571
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.` ) ;
25832586 }
2587+ if ( ! negative && ! foundDiagnostic ) {
2588+ this . raiseError ( `verifyRefactorDiagnosticsAvailableAtMarker failed - expected a refactor diagnostic at marker ${ markerName } but found none.` ) ;
2589+ }
2590+ }
25842591
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.` ) ;
25892598 }
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 .` ) ;
25922601 }
25932602 }
25942603
2595- public verifyFileAfterApplyingRefactors ( expectedContent : string , formattingOptions ?: ts . FormatCodeSettings ) {
2604+ public verifyApplicableRefactorAvailableForRange ( negative : boolean ) {
25962605 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 ) ;
26132645 const actualContent = this . getFileContent ( this . activeFile . fileName ) ;
26142646
26152647 if ( this . normalizeNewlines ( actualContent ) !== this . normalizeNewlines ( expectedContent ) ) {
@@ -3409,8 +3441,16 @@ namespace FourSlashInterface {
34093441 this . state . verifyCodeFixAvailable ( this . negative ) ;
34103442 }
34113443
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 ) ;
34143454 }
34153455 }
34163456
@@ -3618,8 +3658,8 @@ namespace FourSlashInterface {
36183658 this . state . verifyRangeAfterCodeFix ( expectedText , includeWhiteSpace , errorCode , index ) ;
36193659 }
36203660
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 ) ;
36233663 }
36243664
36253665 public importFixAtPosition ( expectedTextArray : string [ ] , errorCode ?: number ) : void {
0 commit comments