Skip to content

Commit

Permalink
Add refactoring 'Remove directive (including content)' (#1224)
Browse files Browse the repository at this point in the history
  • Loading branch information
josefpihrt committed Oct 18, 2023
1 parent cfae950 commit 6fd43e1
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 1 deletion.
1 change: 1 addition & 0 deletions ChangeLog.md
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

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

### Changed

Expand Down
38 changes: 37 additions & 1 deletion src/CSharp.Workspaces/CSharp/Extensions/WorkspaceExtensions.cs
Expand Up @@ -2,6 +2,7 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
Expand Down Expand Up @@ -281,6 +282,7 @@ internal static DefaultSyntaxOptions GetDefaultSyntaxOptions(this Document docum
internal static async Task<Document> RemovePreprocessorDirectivesAsync(
this Document document,
IEnumerable<DirectiveTriviaSyntax> directives,
PreprocessorDirectiveRemoveOptions options,
CancellationToken cancellationToken = default)
{
if (document is null)
Expand All @@ -291,7 +293,9 @@ internal static DefaultSyntaxOptions GetDefaultSyntaxOptions(this Document docum

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

SourceText newSourceText = sourceText.WithChanges(GetTextChanges());
SourceText newSourceText = ((options & PreprocessorDirectiveRemoveOptions.IncludeContent) != 0)
? RemovePreprocessorDirectivesIncludingContent(sourceText, directives)
: sourceText.WithChanges(GetTextChanges());

return document.WithText(newSourceText);

Expand All @@ -308,6 +312,38 @@ IEnumerable<TextChange> GetTextChanges()
}
}

private static SourceText RemovePreprocessorDirectivesIncludingContent(
SourceText sourceText,
IEnumerable<DirectiveTriviaSyntax> directives)
{
SourceText newSourceText = sourceText;
IEnumerable<DirectiveTriviaSyntax> sortedDirectives = directives.OrderBy(f => f.SpanStart);

DirectiveTriviaSyntax firstDirective = sortedDirectives.FirstOrDefault();

int spanStart = firstDirective.FullSpan.Start;
SyntaxTrivia parentTrivia = firstDirective.ParentTrivia;
SyntaxTriviaList triviaList = parentTrivia.GetContainingList();
int parentTriviaIndex = triviaList.IndexOf(parentTrivia);

if (parentTriviaIndex > 0)
{
SyntaxTrivia previousTrivia = triviaList[parentTriviaIndex - 1];

if (previousTrivia.IsWhitespaceTrivia())
spanStart = previousTrivia.SpanStart;
}

if (firstDirective is not null)
{
TextSpan span = TextSpan.FromBounds(spanStart, sortedDirectives.Last().FullSpan.End);

return sourceText.WithChange(span, "");
}

return sourceText;
}

private static SourceText RemovePreprocessorDirectives(
SourceText sourceText,
IEnumerable<DirectiveTriviaSyntax> directives,
Expand Down
12 changes: 12 additions & 0 deletions src/CSharp.Workspaces/CSharp/PreprocessorDirectiveRemoveOptions.cs
@@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;

namespace Roslynator.CSharp;

[Flags]
internal enum PreprocessorDirectiveRemoveOptions
{
None,
IncludeContent,
}
19 changes: 19 additions & 0 deletions src/Refactorings/CSharp/Refactorings/DirectiveTriviaRefactoring.cs
@@ -1,5 +1,6 @@
// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand All @@ -25,9 +26,27 @@ public static void ComputeRefactorings(RefactoringContext context, DirectiveTriv
{
return context.Document.RemovePreprocessorDirectivesAsync(
directive.GetRelatedDirectives().ToImmutableArray(),
PreprocessorDirectiveRemoveOptions.None,
ct);
},
RefactoringDescriptors.RemovePreprocessorDirective);

List<DirectiveTriviaSyntax> directives = directive.GetRelatedDirectives();

if (directives.Count > 1)
{
context.RegisterRefactoring(
"Remove directive (including content)",
ct =>
{
return context.Document.RemovePreprocessorDirectivesAsync(
directives,
PreprocessorDirectiveRemoveOptions.IncludeContent,
ct);
},
RefactoringDescriptors.RemovePreprocessorDirective,
"IncludingContent");
}
}
}
}
138 changes: 138 additions & 0 deletions src/Tests/Refactorings.Tests/RR0100RemovePreprocessorDirectiveTests.cs
@@ -0,0 +1,138 @@
// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Threading.Tasks;
using Roslynator.Testing.CSharp;
using Xunit;

namespace Roslynator.CSharp.Refactorings.Tests;

public class RR0100RemovePreprocessorDirectiveTests : AbstractCSharpRefactoringVerifier
{
public override string RefactoringId { get; } = RefactoringIdentifiers.RemovePreprocessorDirective;

[Fact, Trait(Traits.Refactoring, RefactoringIdentifiers.RemovePreprocessorDirective)]
public async Task RemovePreprocessorDirectiveIncludingContent_If()
{
await VerifyRefactoringAsync(@"
class C
{
#if [||]DEBUG // if
void M()
{
}
#endif // endif
}
", @"
class C
{
}
", equivalenceKey: EquivalenceKey.Create(RefactoringId) + ".IncludingContent");
}

[Fact, Trait(Traits.Refactoring, RefactoringIdentifiers.RemovePreprocessorDirective)]
public async Task RemovePreprocessorDirectiveIncludingContent_EndIf()
{
await VerifyRefactoringAsync(@"
class C
{
#if DEBUG // if
void M()
{
}
#[||]endif // endif
}
", @"
class C
{
}
", equivalenceKey: EquivalenceKey.Create(RefactoringId) + ".IncludingContent");
}

[Fact, Trait(Traits.Refactoring, RefactoringIdentifiers.RemovePreprocessorDirective)]
public async Task RemovePreprocessorDirectiveIncludingContent_Else()
{
await VerifyRefactoringAsync(@"
class C
{
#if DEBUG // if
void M()
{
}
#[||]else
void M2()
{
}
#endif // endif
}
", @"
class C
{
}
", equivalenceKey: EquivalenceKey.Create(RefactoringId) + ".IncludingContent");
}

[Fact, Trait(Traits.Refactoring, RefactoringIdentifiers.RemovePreprocessorDirective)]
public async Task RemovePreprocessorDirectiveIncludingContent_Elif()
{
await VerifyRefactoringAsync(@"
class C
{
#if DEBUG // if
void M()
{
}
#[||]elif FOO
void M2()
{
}
#else
void M3()
{
}
#endif // endif
}
", @"
class C
{
}
", equivalenceKey: EquivalenceKey.Create(RefactoringId) + ".IncludingContent");
}

[Fact, Trait(Traits.Refactoring, RefactoringIdentifiers.RemovePreprocessorDirective)]
public async Task RemovePreprocessorDirectiveIncludingContent_Region()
{
await VerifyRefactoringAsync(@"
class C
{
#[||]region // region
void M()
{
}
#endregion // endregion
}
", @"
class C
{
}
", equivalenceKey: EquivalenceKey.Create(RefactoringId) + ".IncludingContent");
}

[Fact, Trait(Traits.Refactoring, RefactoringIdentifiers.RemovePreprocessorDirective)]
public async Task RemovePreprocessorDirectiveIncludingContent_EndRegion()
{
await VerifyRefactoringAsync(@"
class C
{
#region // region
void M()
{
}
#[||]endregion // endregion
}
", @"
class C
{
}
", equivalenceKey: EquivalenceKey.Create(RefactoringId) + ".IncludingContent");
}
}

0 comments on commit 6fd43e1

Please sign in to comment.