Skip to content

Commit 6fd43e1

Browse files
authored
Add refactoring 'Remove directive (including content)' (#1224)
1 parent cfae950 commit 6fd43e1

File tree

5 files changed

+207
-1
lines changed

5 files changed

+207
-1
lines changed

ChangeLog.md

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- Add social card ([#1212](https://github.com/dotnet/roslynator/pull/1212)).
1313
- Add nullable annotation to public API ([#1198](https://github.com/JosefPihrt/Roslynator/pull/1198)).
14+
- Add refactoring "Remove directive (including content)" ([#1224](https://github.com/dotnet/roslynator/pull/1224)).
1415

1516
### Changed
1617

src/CSharp.Workspaces/CSharp/Extensions/WorkspaceExtensions.cs

+37-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
using System;
44
using System.Collections.Generic;
5+
using System.Linq;
56
using System.Threading;
67
using System.Threading.Tasks;
78
using Microsoft.CodeAnalysis;
@@ -281,6 +282,7 @@ public static async Task<Document> RemovePreprocessorDirectivesAsync(
281282
internal static async Task<Document> RemovePreprocessorDirectivesAsync(
282283
this Document document,
283284
IEnumerable<DirectiveTriviaSyntax> directives,
285+
PreprocessorDirectiveRemoveOptions options,
284286
CancellationToken cancellationToken = default)
285287
{
286288
if (document is null)
@@ -291,7 +293,9 @@ internal static async Task<Document> RemovePreprocessorDirectivesAsync(
291293

292294
SourceText sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
293295

294-
SourceText newSourceText = sourceText.WithChanges(GetTextChanges());
296+
SourceText newSourceText = ((options & PreprocessorDirectiveRemoveOptions.IncludeContent) != 0)
297+
? RemovePreprocessorDirectivesIncludingContent(sourceText, directives)
298+
: sourceText.WithChanges(GetTextChanges());
295299

296300
return document.WithText(newSourceText);
297301

@@ -308,6 +312,38 @@ IEnumerable<TextChange> GetTextChanges()
308312
}
309313
}
310314

315+
private static SourceText RemovePreprocessorDirectivesIncludingContent(
316+
SourceText sourceText,
317+
IEnumerable<DirectiveTriviaSyntax> directives)
318+
{
319+
SourceText newSourceText = sourceText;
320+
IEnumerable<DirectiveTriviaSyntax> sortedDirectives = directives.OrderBy(f => f.SpanStart);
321+
322+
DirectiveTriviaSyntax firstDirective = sortedDirectives.FirstOrDefault();
323+
324+
int spanStart = firstDirective.FullSpan.Start;
325+
SyntaxTrivia parentTrivia = firstDirective.ParentTrivia;
326+
SyntaxTriviaList triviaList = parentTrivia.GetContainingList();
327+
int parentTriviaIndex = triviaList.IndexOf(parentTrivia);
328+
329+
if (parentTriviaIndex > 0)
330+
{
331+
SyntaxTrivia previousTrivia = triviaList[parentTriviaIndex - 1];
332+
333+
if (previousTrivia.IsWhitespaceTrivia())
334+
spanStart = previousTrivia.SpanStart;
335+
}
336+
337+
if (firstDirective is not null)
338+
{
339+
TextSpan span = TextSpan.FromBounds(spanStart, sortedDirectives.Last().FullSpan.End);
340+
341+
return sourceText.WithChange(span, "");
342+
}
343+
344+
return sourceText;
345+
}
346+
311347
private static SourceText RemovePreprocessorDirectives(
312348
SourceText sourceText,
313349
IEnumerable<DirectiveTriviaSyntax> directives,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
2+
3+
using System;
4+
5+
namespace Roslynator.CSharp;
6+
7+
[Flags]
8+
internal enum PreprocessorDirectiveRemoveOptions
9+
{
10+
None,
11+
IncludeContent,
12+
}

src/Refactorings/CSharp/Refactorings/DirectiveTriviaRefactoring.cs

+19
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
22

3+
using System.Collections.Generic;
34
using System.Collections.Immutable;
45
using Microsoft.CodeAnalysis.CSharp;
56
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -25,9 +26,27 @@ public static void ComputeRefactorings(RefactoringContext context, DirectiveTriv
2526
{
2627
return context.Document.RemovePreprocessorDirectivesAsync(
2728
directive.GetRelatedDirectives().ToImmutableArray(),
29+
PreprocessorDirectiveRemoveOptions.None,
2830
ct);
2931
},
3032
RefactoringDescriptors.RemovePreprocessorDirective);
33+
34+
List<DirectiveTriviaSyntax> directives = directive.GetRelatedDirectives();
35+
36+
if (directives.Count > 1)
37+
{
38+
context.RegisterRefactoring(
39+
"Remove directive (including content)",
40+
ct =>
41+
{
42+
return context.Document.RemovePreprocessorDirectivesAsync(
43+
directives,
44+
PreprocessorDirectiveRemoveOptions.IncludeContent,
45+
ct);
46+
},
47+
RefactoringDescriptors.RemovePreprocessorDirective,
48+
"IncludingContent");
49+
}
3150
}
3251
}
3352
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
2+
3+
using System.Threading.Tasks;
4+
using Roslynator.Testing.CSharp;
5+
using Xunit;
6+
7+
namespace Roslynator.CSharp.Refactorings.Tests;
8+
9+
public class RR0100RemovePreprocessorDirectiveTests : AbstractCSharpRefactoringVerifier
10+
{
11+
public override string RefactoringId { get; } = RefactoringIdentifiers.RemovePreprocessorDirective;
12+
13+
[Fact, Trait(Traits.Refactoring, RefactoringIdentifiers.RemovePreprocessorDirective)]
14+
public async Task RemovePreprocessorDirectiveIncludingContent_If()
15+
{
16+
await VerifyRefactoringAsync(@"
17+
class C
18+
{
19+
#if [||]DEBUG // if
20+
void M()
21+
{
22+
}
23+
#endif // endif
24+
}
25+
", @"
26+
class C
27+
{
28+
}
29+
", equivalenceKey: EquivalenceKey.Create(RefactoringId) + ".IncludingContent");
30+
}
31+
32+
[Fact, Trait(Traits.Refactoring, RefactoringIdentifiers.RemovePreprocessorDirective)]
33+
public async Task RemovePreprocessorDirectiveIncludingContent_EndIf()
34+
{
35+
await VerifyRefactoringAsync(@"
36+
class C
37+
{
38+
#if DEBUG // if
39+
void M()
40+
{
41+
}
42+
#[||]endif // endif
43+
}
44+
", @"
45+
class C
46+
{
47+
}
48+
", equivalenceKey: EquivalenceKey.Create(RefactoringId) + ".IncludingContent");
49+
}
50+
51+
[Fact, Trait(Traits.Refactoring, RefactoringIdentifiers.RemovePreprocessorDirective)]
52+
public async Task RemovePreprocessorDirectiveIncludingContent_Else()
53+
{
54+
await VerifyRefactoringAsync(@"
55+
class C
56+
{
57+
#if DEBUG // if
58+
void M()
59+
{
60+
}
61+
#[||]else
62+
void M2()
63+
{
64+
}
65+
#endif // endif
66+
}
67+
", @"
68+
class C
69+
{
70+
}
71+
", equivalenceKey: EquivalenceKey.Create(RefactoringId) + ".IncludingContent");
72+
}
73+
74+
[Fact, Trait(Traits.Refactoring, RefactoringIdentifiers.RemovePreprocessorDirective)]
75+
public async Task RemovePreprocessorDirectiveIncludingContent_Elif()
76+
{
77+
await VerifyRefactoringAsync(@"
78+
class C
79+
{
80+
#if DEBUG // if
81+
void M()
82+
{
83+
}
84+
#[||]elif FOO
85+
void M2()
86+
{
87+
}
88+
#else
89+
void M3()
90+
{
91+
}
92+
#endif // endif
93+
}
94+
", @"
95+
class C
96+
{
97+
}
98+
", equivalenceKey: EquivalenceKey.Create(RefactoringId) + ".IncludingContent");
99+
}
100+
101+
[Fact, Trait(Traits.Refactoring, RefactoringIdentifiers.RemovePreprocessorDirective)]
102+
public async Task RemovePreprocessorDirectiveIncludingContent_Region()
103+
{
104+
await VerifyRefactoringAsync(@"
105+
class C
106+
{
107+
#[||]region // region
108+
void M()
109+
{
110+
}
111+
#endregion // endregion
112+
}
113+
", @"
114+
class C
115+
{
116+
}
117+
", equivalenceKey: EquivalenceKey.Create(RefactoringId) + ".IncludingContent");
118+
}
119+
120+
[Fact, Trait(Traits.Refactoring, RefactoringIdentifiers.RemovePreprocessorDirective)]
121+
public async Task RemovePreprocessorDirectiveIncludingContent_EndRegion()
122+
{
123+
await VerifyRefactoringAsync(@"
124+
class C
125+
{
126+
#region // region
127+
void M()
128+
{
129+
}
130+
#[||]endregion // endregion
131+
}
132+
", @"
133+
class C
134+
{
135+
}
136+
", equivalenceKey: EquivalenceKey.Create(RefactoringId) + ".IncludingContent");
137+
}
138+
}

0 commit comments

Comments
 (0)