77using System . Collections . Immutable ;
88using System . Diagnostics ;
99using System . Linq ;
10+ using System . Reflection . Emit ;
1011using System . Threading ;
1112using System . Threading . Tasks ;
1213using Microsoft . CodeAnalysis . CodeActions ;
@@ -26,11 +27,13 @@ internal abstract partial class AbstractInvertIfCodeRefactoringProvider<
2627 TStatementSyntax ,
2728 TIfStatementSyntax ,
2829 TEmbeddedStatementSyntax ,
29- TDirectiveSyntaxSyntax > : CodeRefactoringProvider
30+ TDirectiveSyntax ,
31+ TIfDirectiveSyntax > : CodeRefactoringProvider
3032 where TSyntaxKind : struct , Enum
3133 where TStatementSyntax : SyntaxNode
3234 where TIfStatementSyntax : TStatementSyntax
33- where TDirectiveSyntaxSyntax : SyntaxNode
35+ where TDirectiveSyntax : SyntaxNode
36+ where TIfDirectiveSyntax : TDirectiveSyntax
3437{
3538 private enum InvertIfStyle
3639 {
@@ -63,7 +66,9 @@ private enum InvertIfStyle
6366 protected abstract bool IsElseless ( TIfStatementSyntax ifNode ) ;
6467
6568 protected abstract StatementRange GetIfBodyStatementRange ( TIfStatementSyntax ifNode ) ;
69+
6670 protected abstract SyntaxNode GetCondition ( TIfStatementSyntax ifNode ) ;
71+ protected abstract SyntaxNode GetCondition ( TIfDirectiveSyntax ifNode ) ;
6772
6873 protected abstract IEnumerable < TStatementSyntax > UnwrapBlock ( TEmbeddedStatementSyntax ifBody ) ;
6974 protected abstract TEmbeddedStatementSyntax GetIfBody ( TIfStatementSyntax ifNode ) ;
@@ -102,28 +107,89 @@ private async ValueTask<bool> TryComputeRefactoringForIfDirectiveAsync(CodeRefac
102107 var root = await document . GetRequiredSyntaxRootAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
103108
104109 var token = root . FindToken ( textSpan . Start , findInsideTrivia : true ) ;
105- var directive = token . GetAncestor < TDirectiveSyntaxSyntax > ( ) ;
106- if ( directive is null )
110+ var ifDirective = token . GetAncestor < TIfDirectiveSyntax > ( ) ;
111+ if ( ifDirective is null )
112+ return false ;
113+
114+ if ( HasErrorDiagnostics ( ifDirective ) )
107115 return false ;
108116
109117 var syntaxFacts = document . GetRequiredLanguageService < ISyntaxFactsService > ( ) ;
110118 var syntaxKinds = syntaxFacts . SyntaxKinds ;
111119
112- if ( directive . RawKind != syntaxKinds . IfDirectiveTrivia )
120+ if ( ifDirective . RawKind != syntaxKinds . IfDirectiveTrivia )
113121 return false ;
114122
115- var conditionalDirectives = syntaxFacts . GetMatchingConditionalDirectives ( directive , cancellationToken ) ;
123+ var conditionalDirectives = syntaxFacts . GetMatchingConditionalDirectives ( ifDirective , cancellationToken ) ;
116124 if ( conditionalDirectives . Length != 3 )
117125 return false ;
118126
119- if ( conditionalDirectives [ 0 ] . RawKind != syntaxKinds . IfDirectiveTrivia ||
127+ if ( conditionalDirectives [ 0 ] != ifDirective ||
120128 conditionalDirectives [ 1 ] . RawKind != syntaxKinds . ElseDirectiveTrivia ||
121129 conditionalDirectives [ 2 ] . RawKind != syntaxKinds . EndIfDirectiveTrivia )
122130 {
123131 return false ;
124132 }
133+
134+ var elseDirective = ( TDirectiveSyntax ) conditionalDirectives [ 1 ] ;
135+ var endIfDirective = ( TDirectiveSyntax ) conditionalDirectives [ 2 ] ;
136+
137+ if ( HasErrorDiagnostics ( elseDirective ) ||
138+ HasErrorDiagnostics ( endIfDirective ) )
139+ {
140+ return false ;
141+ }
142+
143+ var title = GetTitle ( ) ;
144+ context . RegisterRefactoring ( CodeAction . Create (
145+ title ,
146+ cancellationToken => InvertIfDirectiveAsync ( document , ifDirective , elseDirective , endIfDirective , cancellationToken ) ,
147+ title ) ,
148+ ifDirective . Span ) ;
149+ return true ;
125150 }
126151
152+ private async Task < Document > InvertIfDirectiveAsync (
153+ Document document ,
154+ TIfDirectiveSyntax ifDirective ,
155+ TDirectiveSyntax elseDirective ,
156+ TDirectiveSyntax endIfDirective ,
157+ CancellationToken cancellationToken )
158+ {
159+ var semanticModel = await document . GetRequiredSemanticModelAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
160+ var generator = document . GetRequiredLanguageService < SyntaxGenerator > ( ) ;
161+
162+ var condition = GetCondition ( ifDirective ) ;
163+ var invertedCondition = generator . Negate (
164+ generator . SyntaxGeneratorInternal ,
165+ condition ,
166+ semanticModel ,
167+ cancellationToken ) ;
168+
169+ var text = await document . GetTextAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
170+ var ifDirectiveLine = text . Lines . GetLineFromPosition ( ifDirective . SpanStart ) ;
171+ var elseDirectiveLine = text . Lines . GetLineFromPosition ( elseDirective . SpanStart ) ;
172+ var endIfDirectiveLine = text . Lines . GetLineFromPosition ( endIfDirective . SpanStart ) ;
173+
174+ var trueSpanStart = text . Lines [ ifDirectiveLine . LineNumber + 1 ] . Start ;
175+ var trueSpan = TextSpan . FromBounds ( trueSpanStart , Math . Max ( trueSpanStart , text . Lines [ elseDirectiveLine . LineNumber - 1 ] . SpanIncludingLineBreak . End ) ) ;
176+
177+ var falseSpanStart = text . Lines [ elseDirectiveLine . LineNumber + 1 ] . Start ;
178+ var falseSpan = TextSpan . FromBounds ( falseSpanStart , Math . Max ( falseSpanStart , text . Lines [ endIfDirectiveLine . LineNumber - 1 ] . SpanIncludingLineBreak . End ) ) ;
179+
180+ // Swap the condition with the new condition.
181+ // Swap the true/false sections.
182+ var newText = text . WithChanges (
183+ new TextChange ( condition . FullSpan , invertedCondition . ToFullString ( ) ) ,
184+ new TextChange ( trueSpan , text . ToString ( falseSpan ) ) ,
185+ new TextChange ( falseSpan , text . ToString ( trueSpan ) ) ) ;
186+
187+ return document . WithText ( newText ) ;
188+ }
189+
190+ private static bool HasErrorDiagnostics ( SyntaxNode node )
191+ => node . GetDiagnostics ( ) . Any ( static d => d . Severity == DiagnosticSeverity . Error ) ;
192+
127193 private async ValueTask TryComputeRefactorForIfStatementAsync ( CodeRefactoringContext context )
128194 {
129195 var ( document , textSpan , cancellationToken ) = context ;
@@ -135,11 +201,10 @@ private async ValueTask TryComputeRefactorForIfStatementAsync(CodeRefactoringCon
135201 return ;
136202
137203 var title = GetTitle ( ) ;
138- context . RegisterRefactoring (
139- CodeAction . Create (
140- title ,
141- c => InvertIfAsync ( document , ifNode , c ) ,
142- title ) ,
204+ context . RegisterRefactoring ( CodeAction . Create (
205+ title ,
206+ cancellationToken => InvertIfAsync ( document , ifNode , cancellationToken ) ,
207+ title ) ,
143208 ifNode . Span ) ;
144209 }
145210
0 commit comments