Skip to content

Commit d10ff49

Browse files
Flesh out
1 parent 6196f4f commit d10ff49

File tree

3 files changed

+84
-14
lines changed

3 files changed

+84
-14
lines changed

src/Features/CSharp/Portable/InvertIf/CSharpInvertIfCodeRefactoringProvider.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ namespace Microsoft.CodeAnalysis.CSharp.InvertIf;
2525
[method: ImportingConstructor]
2626
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
2727
internal sealed class CSharpInvertIfCodeRefactoringProvider() : AbstractInvertIfCodeRefactoringProvider<
28-
SyntaxKind, StatementSyntax, IfStatementSyntax, StatementSyntax>
28+
SyntaxKind,
29+
StatementSyntax,
30+
IfStatementSyntax,
31+
StatementSyntax,
32+
IfDirectiveTriviaSyntax>
2933
{
3034
protected override string GetTitle()
3135
=> CSharpFeaturesResources.Invert_if;

src/Features/Core/Portable/InvertIf/AbstractInvertIfCodeRefactoringProvider.StatementRange.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ internal abstract partial class AbstractInvertIfCodeRefactoringProvider<
1212
TStatementSyntax,
1313
TIfStatementSyntax,
1414
TEmbeddedStatementSyntax,
15-
TDirectiveSyntaxSyntax>
15+
TDirectiveSyntax,
16+
TIfDirectiveSyntax>
1617
{
1718
protected readonly struct StatementRange
1819
{

src/Features/Core/Portable/InvertIf/AbstractInvertIfCodeRefactoringProvider.cs

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Collections.Immutable;
88
using System.Diagnostics;
99
using System.Linq;
10+
using System.Reflection.Emit;
1011
using System.Threading;
1112
using System.Threading.Tasks;
1213
using 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

Comments
 (0)