File: CodeCleanup\CodeCleanupTests.cs
Web Access
Project: src\src\Workspaces\CoreTest\Microsoft.CodeAnalysis.Workspaces.UnitTests.csproj (Microsoft.CodeAnalysis.Workspaces.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeCleanup;
using Microsoft.CodeAnalysis.CodeCleanup.Providers;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.UnitTests.CodeCleanup
{
    using CSharp = Microsoft.CodeAnalysis.CSharp;
 
    [UseExportProvider]
    public class CodeCleanupTests
    {
        #region CSharp Code CodeCleaner Tests
        [Fact]
        public void DefaultCSharpCodeCleanups()
        {
            var document = CreateDocument("class C { }", LanguageNames.CSharp);
            var codeCleanups = CodeCleaner.GetDefaultProviders(document);
            Assert.NotEmpty(codeCleanups);
        }
 
        [Fact]
        public async Task CodeCleanersCSharp_NoSpans()
        {
            var document = CreateDocument("class C { }", LanguageNames.CSharp);
            var cleanDocument = await CodeCleaner.CleanupAsync(document, [], await document.GetCodeCleanupOptionsAsync(CancellationToken.None));
 
            Assert.Equal(document, cleanDocument);
        }
 
        [Fact]
        public async Task CodeCleanersCSharp_Document()
        {
            var document = CreateDocument("class C { }", LanguageNames.CSharp);
            var cleanDocument = await CodeCleaner.CleanupAsync(document, await document.GetCodeCleanupOptionsAsync(CancellationToken.None));
 
            Assert.Equal(document, cleanDocument);
        }
 
        [Fact]
        public async Task CodeCleanersCSharp_Span()
        {
            var document = CreateDocument("class C { }", LanguageNames.CSharp);
            var root = await document.GetSyntaxRootAsync();
            var cleanDocument = await CodeCleaner.CleanupAsync(document, root.FullSpan, await document.GetCodeCleanupOptionsAsync(CancellationToken.None));
 
            Assert.Equal(document, cleanDocument);
        }
 
        [Fact]
        public async Task CodeCleanersCSharp_Spans()
        {
            var document = CreateDocument("class C { }", LanguageNames.CSharp);
            var root = await document.GetSyntaxRootAsync();
            var cleanDocument = await CodeCleaner.CleanupAsync(document, [root.FullSpan], await document.GetCodeCleanupOptionsAsync(CancellationToken.None));
 
            Assert.Equal(document, cleanDocument);
        }
 
        #endregion
 
        #region Visual Basic Code CodeCleaner Tests
 
        [Fact]
        public void DefaultVisualBasicCodeCleanups()
        {
            var document = CreateDocument(@"Class C
End Class", LanguageNames.VisualBasic);
            var codeCleanups = CodeCleaner.GetDefaultProviders(document);
            Assert.NotEmpty(codeCleanups);
        }
 
        [Fact]
        public async Task CodeCleanersVisualBasic_NoSpans()
        {
            var document = CreateDocument(@"Class C
End Class", LanguageNames.VisualBasic);
            var cleanDocument = await CodeCleaner.CleanupAsync(document, [], await document.GetCodeCleanupOptionsAsync(CancellationToken.None));
 
            Assert.Equal(document, cleanDocument);
        }
 
        [Fact]
        public async Task CodeCleanersVisualBasic_Document()
        {
            var document = CreateDocument(@"Class C
End Class", LanguageNames.VisualBasic);
            var cleanDocument = await CodeCleaner.CleanupAsync(document, await document.GetCodeCleanupOptionsAsync(CancellationToken.None));
 
            Assert.Equal(document, cleanDocument);
        }
 
        [Fact]
        public async Task CodeCleanersVisualBasic_Span()
        {
            var document = CreateDocument(@"Class C
End Class", LanguageNames.VisualBasic);
            var root = await document.GetSyntaxRootAsync();
            var cleanDocument = await CodeCleaner.CleanupAsync(document, root.FullSpan, await document.GetCodeCleanupOptionsAsync(CancellationToken.None));
 
            Assert.Equal(document, cleanDocument);
        }
 
        [Fact]
        public async Task CodeCleanersVisualBasic_Spans()
        {
            var document = CreateDocument(@"Class C
End Class", LanguageNames.VisualBasic);
            var root = await document.GetSyntaxRootAsync();
            var cleanDocument = await CodeCleaner.CleanupAsync(document, [root.FullSpan], await document.GetCodeCleanupOptionsAsync(CancellationToken.None));
 
            Assert.Equal(document, cleanDocument);
        }
 
        [Fact]
        public async Task CodeCleanersCSharp_Annotation()
        {
            var document = CreateDocument("class C { }", LanguageNames.CSharp);
            var annotation = new SyntaxAnnotation();
            document = document.WithSyntaxRoot((await document.GetSyntaxRootAsync()).WithAdditionalAnnotations(annotation));
 
            var cleanDocument = await CodeCleaner.CleanupAsync(document, annotation, await document.GetCodeCleanupOptionsAsync(CancellationToken.None));
 
            Assert.Equal(document, cleanDocument);
        }
 
        [Fact]
        public async Task CodeCleanersVisualBasic_Annotation()
        {
            var document = CreateDocument(@"Class C
End Class", LanguageNames.VisualBasic);
            var annotation = new SyntaxAnnotation();
            document = document.WithSyntaxRoot((await document.GetSyntaxRootAsync()).WithAdditionalAnnotations(annotation));
 
            var cleanDocument = await CodeCleaner.CleanupAsync(document, annotation, await document.GetCodeCleanupOptionsAsync(CancellationToken.None));
 
            Assert.Equal(document, cleanDocument);
        }
 
        #endregion
 
        [Fact]
        public Task EntireRange()
            => VerifyRange("{|b:{|r:class C {}|}|}");
 
        [Fact]
        public Task EntireRange_Merge()
            => VerifyRange("{|r:class {|b:C { }|} class {|b: B { } |}|}");
 
        [Fact]
        public Task EntireRange_EndOfFile()
            => VerifyRange("{|r:class {|b:C { }|} class {|b: B { } |} |}");
 
        [Fact]
        public async Task EntireRangeWithTransformation_RemoveClass()
        {
            var transformer = new MockCodeCleanupProvider()
            {
                CleanupDocumentAsyncImpl = async (provider, document, spans, options, cancellationToken) =>
                {
                    var root = await document.GetSyntaxRootAsync(cancellationToken);
                    root = root.RemoveCSharpMember(0);
 
                    provider.ExpectedResult = [root.FullSpan];
 
                    return document.WithSyntaxRoot(root);
                }
            };
 
            await VerifyRange("{|b:class C {}|}", transformer);
        }
 
        [Fact]
        public async Task EntireRangeWithTransformation_AddMember()
        {
            var transformer = new MockCodeCleanupProvider()
            {
                CleanupDocumentAsyncImpl = async (provider, document, spans, options, cancellationToken) =>
                {
                    var root = await document.GetSyntaxRootAsync(cancellationToken);
                    var @class = root.GetMember(0);
                    var classWithMember = @class.AddCSharpMember(CreateCSharpMethod(), 0);
                    root = root.ReplaceNode(@class, classWithMember);
 
                    provider.ExpectedResult = [root.FullSpan];
 
                    return document.WithSyntaxRoot(root);
                }
            };
 
            await VerifyRange("{|b:class C {}|}", transformer);
        }
 
        [Fact]
        public async Task RangeWithTransformation_AddMember()
        {
            var transformer = new MockCodeCleanupProvider()
            {
                CleanupDocumentAsyncImpl = async (provider, document, spans, options, cancellationToken) =>
                {
                    var root = await document.GetSyntaxRootAsync(cancellationToken);
                    var @class = root.GetMember(0).GetMember(0);
                    var classWithMember = @class.AddCSharpMember(CreateCSharpMethod(), 0);
                    root = root.ReplaceNode(@class, classWithMember);
 
                    provider.ExpectedResult = [root.GetMember(0).GetMember(0).GetCodeCleanupSpan()];
 
                    return document.WithSyntaxRoot(root);
                }
            };
 
            await VerifyRange("namespace N { {|b:class C {}|} }", transformer);
        }
 
        [Fact]
        public async Task RangeWithTransformation_RemoveMember()
        {
            var transformer = new MockCodeCleanupProvider()
            {
                CleanupDocumentAsyncImpl = async (provider, document, spans, options, cancellationToken) =>
                {
                    var root = await document.GetSyntaxRootAsync(cancellationToken);
                    var @class = root.GetMember(0).GetMember(0);
                    var classWithMember = @class.RemoveCSharpMember(0);
                    root = root.ReplaceNode(@class, classWithMember);
 
                    provider.ExpectedResult = [root.GetMember(0).GetMember(0).GetCodeCleanupSpan()];
 
                    return document.WithSyntaxRoot(root);
                }
            };
 
            await VerifyRange("namespace N { {|b:class C { void Method() { } }|} }", transformer);
        }
 
        [Fact]
        public Task MultipleRange_Overlapped()
            => VerifyRange("namespace N {|r:{ {|b:class C { {|b:void Method() { }|} }|} }|}");
 
        [Fact]
        public Task MultipleRange_Adjacent()
            => VerifyRange("namespace N {|r:{ {|b:class C { |}{|b:void Method() { } }|} }|}");
 
        [Fact]
        public Task MultipleRanges()
            => VerifyRange("namespace N { class C {|r:{ {|b:void Method() { }|} }|} class C2 {|r:{ {|b:void Method() { }|} }|} }");
 
        [Fact, WorkItem(12848, "DevDiv_Projects/Roslyn")]
        public async Task DoNotCrash_VB()
        {
            var code = @"#If DEBUG OrElse TRACE Then
Imports System.Diagnostics
#ElseIf SILVERLIGHT Then
Imports System.Diagnostics
#Else
Imports System.Diagnostics
#End If
 
{|r:# {|b: |}
    Region|} ""Region""
#Region ""more""
#End Region 
#End Region";
 
            await VerifyRange(code, LanguageNames.VisualBasic);
        }
 
        [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/774295")]
        public async Task DoNotCrash_VB_2()
        {
            var code = @"
Public Class Class1
    Public Custom Event Event2 As EventHandler
        AddHandler(ByVal value As EventHandler)
        End AddHandler
        RemoveHandler(ByVal value As EventHandler)
        End RemoveHandler
        RaiseEvent(ByVal sender As Object, ByVal e As EventArgs)
            e
    End Event
 
End Class
";
            var document = CreateDocument(code, LanguageNames.VisualBasic);
            var semanticModel = await document.GetSemanticModelAsync();
            var root = await document.GetSyntaxRootAsync();
            var accessor = root.DescendantNodes().OfType<VisualBasic.Syntax.AccessorBlockSyntax>().Last();
            var newSemanticModel = await document.ReuseExistingSpeculativeModelAsync(accessor.Statements[0], CancellationToken.None);
            Assert.NotNull(newSemanticModel);
            Assert.False(newSemanticModel.IsSpeculativeSemanticModel);
 
            var newDocument = CreateDocument(code, LanguageNames.VisualBasic);
            var newRoot = await newDocument.GetSyntaxRootAsync();
            var newAccessor = newRoot.DescendantNodes().OfType<VisualBasic.Syntax.AccessorBlockSyntax>().Last();
            root = root.ReplaceNode(accessor, newAccessor);
            document = document.WithSyntaxRoot(root);
            accessor = root.DescendantNodes().OfType<VisualBasic.Syntax.AccessorBlockSyntax>().Last();
            newSemanticModel = await document.ReuseExistingSpeculativeModelAsync(accessor.Statements[0], CancellationToken.None);
            Assert.NotNull(newSemanticModel);
            Assert.True(newSemanticModel.IsSpeculativeSemanticModel);
 
            var cleanDocument = await CodeCleaner.CleanupAsync(document, await document.GetCodeCleanupOptionsAsync(CancellationToken.None));
            Assert.Equal(document, cleanDocument);
        }
 
        [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/547075")]
        public async Task TestCodeCleanupWithinNonStructuredTrivia()
        {
            var code = @"
#Const ccConst = 0
#If {|b:
|}Then
Imports System
Imports System.Collections.Generic
Imports System.Linq
 
Module Program
    Sub Main(args As String())
 
    End Sub
End Module";
 
            await VerifyRange(code, LanguageNames.VisualBasic);
        }
 
        [Fact]
        public async Task RangeWithTransformation_OutsideOfRange()
        {
            var transformer = new MockCodeCleanupProvider()
            {
                CleanupDocumentAsyncImpl = async (provider, document, spans, options, cancellationToken) =>
                {
                    var root = await document.GetSyntaxRootAsync(cancellationToken);
                    var member = root.GetMember(0).GetMember(0).GetMember(0);
                    var previousToken = member.GetFirstToken().GetPreviousToken().GetPreviousToken();
                    var nextToken = member.GetLastToken().GetNextToken().GetNextToken();
 
                    root = root.ReplaceToken(previousToken, CSharp.SyntaxFactory.Identifier(previousToken.LeadingTrivia, previousToken.ValueText, previousToken.TrailingTrivia));
                    root = root.ReplaceToken(nextToken, CSharp.SyntaxFactory.Token(nextToken.LeadingTrivia, CSharp.CSharpExtensions.Kind(nextToken), nextToken.TrailingTrivia));
 
                    provider.ExpectedResult = [];
 
                    return document.WithSyntaxRoot(root);
                }
            };
 
            await VerifyRange("namespace N { class C { {|b:void Method() { }|} } }", transformer);
        }
 
        public static CSharp.Syntax.MethodDeclarationSyntax CreateCSharpMethod(string returnType = "void", string methodName = "Method")
            => CSharp.SyntaxFactory.MethodDeclaration(CSharp.SyntaxFactory.ParseTypeName(returnType), CSharp.SyntaxFactory.Identifier(methodName));
 
        private static async Task VerifyRange(string codeWithMarker, string language = LanguageNames.CSharp)
        {
            MarkupTestFile.GetSpans(codeWithMarker,
                out var codeWithoutMarker, out IDictionary<string, ImmutableArray<TextSpan>> namedSpans);
 
            var expectedResult = namedSpans.TryGetValue("r", out var spans) ? spans : SpecializedCollections.EmptyEnumerable<TextSpan>();
 
            var transformer = new MockCodeCleanupProvider { ExpectedResult = expectedResult };
 
            await VerifyRange(codeWithoutMarker, [], namedSpans["b"], transformer, language);
        }
 
        private static async Task VerifyRange(string codeWithMarker, MockCodeCleanupProvider transformer, string language = LanguageNames.CSharp)
        {
            MarkupTestFile.GetSpans(codeWithMarker,
                out var codeWithoutMarker, out IDictionary<string, ImmutableArray<TextSpan>> namedSpans);
 
            await VerifyRange(codeWithoutMarker, [transformer], namedSpans["b"], transformer, language);
        }
 
        private static async Task VerifyRange(string code, ImmutableArray<ICodeCleanupProvider> codeCleanups, ImmutableArray<TextSpan> spans, MockCodeCleanupProvider transformer, string language)
        {
            var spanCodeCleanup = new MockCodeCleanupProvider()
            {
                CleanupDocumentAsyncImpl = (provider, document, spans, options, cancellationToken) =>
                {
                    provider.ExpectedResult = spans;
                    return Task.FromResult(document);
                }
            };
 
            var document = CreateDocument(code, language);
 
            await CodeCleaner.CleanupAsync(document, spans,
                await document.GetCodeCleanupOptionsAsync(CancellationToken.None),
                codeCleanups.Concat(spanCodeCleanup));
 
            var sortedSpans = spanCodeCleanup.ExpectedResult.ToList();
            var expectedSpans = transformer.ExpectedResult.ToList();
 
            sortedSpans.Sort();
            expectedSpans.Sort();
 
            AssertEx.Equal(expectedSpans, sortedSpans);
        }
 
        private static Document CreateDocument(string code, string language)
        {
            var solution = new AdhocWorkspace().CurrentSolution;
            var projectId = ProjectId.CreateNewId();
            var project = solution.AddProject(projectId, "Project", "Project.dll", language).GetProject(projectId);
 
            return project.AddDocument("Document", SourceText.From(code));
        }
    }
}