File: CodeGeneration\SymbolEditorTests.cs
Web Access
Project: src\src\Workspaces\CSharpTest\Microsoft.CodeAnalysis.CSharp.Workspaces.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.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.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Formatting;
using Microsoft.CodeAnalysis.CSharp.Simplification;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Simplification;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Editing
{
    [UseExportProvider]
    public class SymbolEditorTests
    {
        private SyntaxGenerator _g;
 
        private SyntaxGenerator Generator
            => _g ??= SyntaxGenerator.GetGenerator(new AdhocWorkspace(), LanguageNames.CSharp);
 
        private static Solution GetSolution(params string[] sources)
        {
            var ws = new AdhocWorkspace();
            var pid = ProjectId.CreateNewId();
 
            var docs = sources.Select((s, i) =>
                DocumentInfo.Create(
                    DocumentId.CreateNewId(pid),
                    name: "code" + i,
                    loader: TextLoader.From(TextAndVersion.Create(SourceText.From(s, encoding: null, SourceHashAlgorithms.Default), VersionStamp.Default)))).ToList();
 
            var proj = ProjectInfo.Create(pid, VersionStamp.Default, "test", "test.dll", LanguageNames.CSharp, documents: docs,
                metadataReferences: [NetFramework.mscorlib]);
 
            return ws.AddProject(proj).Solution;
        }
 
        private static async Task<IEnumerable<ISymbol>> GetSymbolsAsync(Solution solution, string name)
        {
            var compilation = await solution.Projects.First().GetCompilationAsync();
            return compilation.GlobalNamespace.GetMembers(name);
        }
 
        private static async Task<string> GetActualAsync(Document document)
        {
            document = await Simplifier.ReduceAsync(document, CSharpSimplifierOptions.Default, CancellationToken.None);
            document = await Formatter.FormatAsync(document, Formatter.Annotation, CSharpSyntaxFormattingOptions.Default, CancellationToken.None);
            document = await Formatter.FormatAsync(document, SyntaxAnnotation.ElasticAnnotation, CSharpSyntaxFormattingOptions.Default, CancellationToken.None);
            return (await document.GetSyntaxRootAsync()).ToFullString();
        }
 
        [Fact]
        public async Task TestEditOneDeclaration()
        {
            var code =
@"class C
{
}";
 
            var expected =
@"class C
{
    void m()
    {
    }
}";
 
            var solution = GetSolution(code);
            var symbol = (await GetSymbolsAsync(solution, "C")).First();
            var editor = SymbolEditor.Create(solution);
 
            var newSymbol = (INamedTypeSymbol)await editor.EditOneDeclarationAsync(symbol, (e, d) => e.AddMember(d, e.Generator.MethodDeclaration("m")));
            Assert.Equal(1, newSymbol.GetMembers("m").Length);
 
            var actual = await GetActualAsync(editor.GetChangedDocuments().First());
 
            Assert.Equal(expected, actual);
        }
 
        [Fact]
        public async Task TestSequentialEdits()
        {
            var code =
@"class C
{
}";
 
            var expected =
@"class C
{
    void m()
    {
    }
 
    void m2()
    {
    }
}";
 
            var solution = GetSolution(code);
            var symbol = (await GetSymbolsAsync(solution, "C")).First();
            var editor = SymbolEditor.Create(solution);
 
            var newSymbol = (INamedTypeSymbol)await editor.EditOneDeclarationAsync(symbol, (e, d) => e.AddMember(d, Generator.MethodDeclaration("m")));
            Assert.Equal(1, newSymbol.GetMembers("m").Length);
            Assert.Equal(0, newSymbol.GetMembers("m2").Length);
 
            newSymbol = (INamedTypeSymbol)await editor.EditOneDeclarationAsync(symbol, (e, d) => e.AddMember(d, Generator.MethodDeclaration("m2")));
            Assert.Equal(1, newSymbol.GetMembers("m").Length);
            Assert.Equal(1, newSymbol.GetMembers("m2").Length);
 
            var actual = await GetActualAsync(editor.GetChangedDocuments().First());
 
            Assert.Equal(expected, actual);
        }
 
        [Fact]
        public async Task TestSequentialEdit_NewSymbols()
        {
            var code =
@"class C
{
}";
 
            var expected =
@"class C
{
    void m()
    {
    }
 
    void m2()
    {
    }
}";
 
            var solution = GetSolution(code);
            var symbol = (await GetSymbolsAsync(solution, "C")).First();
            var editor = SymbolEditor.Create(solution);
 
            var newSymbol = (INamedTypeSymbol)await editor.EditOneDeclarationAsync(symbol, (e, d) => e.AddMember(d, e.Generator.MethodDeclaration("m")));
            Assert.Equal(1, newSymbol.GetMembers("m").Length);
            Assert.Equal(0, newSymbol.GetMembers("m2").Length);
 
            newSymbol = (INamedTypeSymbol)await editor.EditOneDeclarationAsync(newSymbol, (e, d) => e.AddMember(d, e.Generator.MethodDeclaration("m2")));
            Assert.Equal(1, newSymbol.GetMembers("m").Length);
            Assert.Equal(1, newSymbol.GetMembers("m2").Length);
 
            var actual = await GetActualAsync(editor.GetChangedDocuments().First());
 
            Assert.Equal(expected, actual);
        }
 
        [Fact]
        public async Task TestSequentialEdits_SeparateSymbols()
        {
            var code =
@"class A
{
}
 
class B
{
}";
 
            var expected =
@"class A
{
    void ma()
    {
    }
}
 
class B
{
    void mb()
    {
    }
}";
 
            var solution = GetSolution(code);
            var comp = await solution.Projects.First().GetCompilationAsync();
            var symbolA = comp.GlobalNamespace.GetMembers("A").First();
            var symbolB = comp.GlobalNamespace.GetMembers("B").First();
 
            var editor = SymbolEditor.Create(solution);
 
            var newSymbolA = (INamedTypeSymbol)await editor.EditOneDeclarationAsync(symbolA, (e, d) => e.AddMember(d, e.Generator.MethodDeclaration("ma")));
            Assert.Equal(1, newSymbolA.GetMembers("ma").Length);
 
            var newSymbolB = (INamedTypeSymbol)await editor.EditOneDeclarationAsync(symbolB, (e, d) => e.AddMember(d, e.Generator.MethodDeclaration("mb")));
            Assert.Equal(1, newSymbolB.GetMembers("mb").Length);
 
            var actual = await GetActualAsync(editor.GetChangedDocuments().First());
            Assert.Equal(expected, actual);
        }
 
        [Fact]
        public async Task TestSequentialEdits_SeparateSymbolsAndFiles()
        {
            var code1 =
@"class A
{
}";
 
            var code2 =
@"class B
{
}";
 
            var expected1 =
@"class A
{
    void ma()
    {
    }
}";
 
            var expected2 =
@"class B
{
    void mb()
    {
    }
}";
 
            var solution = GetSolution(code1, code2);
            var comp = await solution.Projects.First().GetCompilationAsync();
            var symbolA = comp.GlobalNamespace.GetMembers("A").First();
            var symbolB = comp.GlobalNamespace.GetMembers("B").First();
 
            var editor = SymbolEditor.Create(solution);
 
            var newSymbolA = (INamedTypeSymbol)await editor.EditOneDeclarationAsync(symbolA, (e, d) => e.AddMember(d, e.Generator.MethodDeclaration("ma")));
            Assert.Equal(1, newSymbolA.GetMembers("ma").Length);
 
            var newSymbolB = (INamedTypeSymbol)await editor.EditOneDeclarationAsync(symbolB, (e, d) => e.AddMember(d, e.Generator.MethodDeclaration("mb")));
            Assert.Equal(1, newSymbolB.GetMembers("mb").Length);
 
            var docs = editor.GetChangedDocuments().ToList();
            var actual1 = await GetActualAsync(docs[0]);
            var actual2 = await GetActualAsync(docs[1]);
 
            Assert.Equal(expected1, actual1);
            Assert.Equal(expected2, actual2);
        }
 
        [Fact]
        public async Task TestEditAllDeclarations_SameFile()
        {
            var code =
@"public partial class C
{
}
 
public partial class C
{
}";
 
            var expected =
@"internal partial class C
{
}
 
internal partial class C
{
}";
 
            var solution = GetSolution(code);
            var symbol = (await GetSymbolsAsync(solution, "C")).First();
            var editor = SymbolEditor.Create(solution);
 
            var newSymbol = (INamedTypeSymbol)await editor.EditAllDeclarationsAsync(symbol, (e, d) => e.SetAccessibility(d, Accessibility.Internal));
 
            var actual = await GetActualAsync(editor.GetChangedDocuments().First());
 
            Assert.Equal(expected, actual);
        }
 
        [Fact]
        public async Task TestEditAllDeclarations_MultipleFiles()
        {
            var code1 =
@"class C
{
}";
 
            var code2 =
@"class C
{
    void M() {}
}";
 
            var expected1 =
@"public class C
{
}";
 
            var expected2 =
@"public class C
{
    void M() {}
}";
 
            var solution = GetSolution(code1, code2);
            var comp = await solution.Projects.First().GetCompilationAsync();
            var symbol = comp.GlobalNamespace.GetMembers("C").First();
 
            var editor = SymbolEditor.Create(solution);
            var newSymbol = (INamedTypeSymbol)await editor.EditAllDeclarationsAsync(symbol, (e, d) => e.SetAccessibility(d, Accessibility.Public));
 
            var docs = editor.GetChangedDocuments().ToList();
            var actual1 = await GetActualAsync(docs[0]);
            var actual2 = await GetActualAsync(docs[1]);
 
            Assert.Equal(expected1, actual1);
            Assert.Equal(expected2, actual2);
        }
 
        [Fact]
        public async Task TestEditDeclarationWithLocation_Last()
        {
            var code =
@"partial class C
{
}
 
partial class C
{
}";
 
            var expected =
@"partial class C
{
}
 
partial class C
{
    void m()
    {
    }
}";
 
            var solution = GetSolution(code);
            var symbol = (await GetSymbolsAsync(solution, "C")).First();
            var location = symbol.Locations.Last();
            var editor = SymbolEditor.Create(solution);
 
            var newSymbol = (INamedTypeSymbol)await editor.EditOneDeclarationAsync(symbol, location, (e, d) => e.AddMember(d, e.Generator.MethodDeclaration("m")));
            Assert.Equal(1, newSymbol.GetMembers("m").Length);
 
            var actual = await GetActualAsync(editor.GetChangedDocuments().First());
 
            Assert.Equal(expected, actual);
        }
 
        [Fact]
        public async Task TestEditDeclarationWithLocation_First()
        {
            var code =
@"partial class C
{
}
 
partial class C
{
}";
 
            var expected =
@"partial class C
{
    void m()
    {
    }
}
 
partial class C
{
}";
 
            var solution = GetSolution(code);
            var symbol = (await GetSymbolsAsync(solution, "C")).First();
            var location = symbol.Locations.First();
            var editor = SymbolEditor.Create(solution);
 
            var newSymbol = (INamedTypeSymbol)await editor.EditOneDeclarationAsync(symbol, location, (e, d) => e.AddMember(d, e.Generator.MethodDeclaration("m")));
            Assert.Equal(1, newSymbol.GetMembers("m").Length);
 
            var actual = await GetActualAsync(editor.GetChangedDocuments().First());
 
            Assert.Equal(expected, actual);
        }
 
        [Fact]
        public async Task TestEditDeclarationWithLocation_SequentialEdits_SameLocation()
        {
            var code =
@"partial class C
{
}
 
partial class C
{
}";
 
            var expected =
@"partial class C
{
}
 
partial class C
{
    void m()
    {
    }
 
    void m2()
    {
    }
}";
 
            var solution = GetSolution(code);
            var symbol = (await GetSymbolsAsync(solution, "C")).First();
            var location = symbol.Locations.Last();
            var editor = SymbolEditor.Create(solution);
 
            var newSymbol = (INamedTypeSymbol)await editor.EditOneDeclarationAsync(symbol, location, (e, d) => e.AddMember(d, e.Generator.MethodDeclaration("m")));
            Assert.Equal(1, newSymbol.GetMembers("m").Length);
 
            // reuse location from original symbol/solution
            var newSymbol2 = (INamedTypeSymbol)await editor.EditOneDeclarationAsync(newSymbol, location, (e, d) => e.AddMember(d, e.Generator.MethodDeclaration("m2")));
            Assert.Equal(1, newSymbol2.GetMembers("m").Length);
            Assert.Equal(1, newSymbol2.GetMembers("m2").Length);
 
            var actual = await GetActualAsync(editor.GetChangedDocuments().First());
 
            Assert.Equal(expected, actual);
        }
 
        [Fact]
        public async Task TestEditDeclarationWithLocation_SequentialEdits_NewLocation()
        {
            var code =
@"partial class C
{
}
 
partial class C
{
}";
 
            var expected =
@"partial class C
{
}
 
partial class C
{
    void m()
    {
    }
 
    void m2()
    {
    }
}";
 
            var solution = GetSolution(code);
            var symbol = (await GetSymbolsAsync(solution, "C")).First();
            var location = symbol.Locations.Last();
            var editor = SymbolEditor.Create(solution);
 
            var newSymbol = (INamedTypeSymbol)await editor.EditOneDeclarationAsync(symbol, location, (e, d) => e.AddMember(d, e.Generator.MethodDeclaration("m")));
            Assert.Equal(1, newSymbol.GetMembers("m").Length);
 
            // use location from new symbol
            var newLocation = newSymbol.Locations.Last();
            var newSymbol2 = (INamedTypeSymbol)await editor.EditOneDeclarationAsync(newSymbol, newLocation, (e, d) => e.AddMember(d, e.Generator.MethodDeclaration("m2")));
            Assert.Equal(1, newSymbol2.GetMembers("m").Length);
            Assert.Equal(1, newSymbol2.GetMembers("m2").Length);
 
            var actual = await GetActualAsync(editor.GetChangedDocuments().First());
 
            Assert.Equal(expected, actual);
        }
 
        [Fact]
        public async Task TestEditDeclarationWithMember()
        {
            var code =
@"partial class C
{
}
 
partial class C
{
    void m()
    {
    }
}";
 
            var expected =
@"partial class C
{
}
 
partial class C
{
    void m()
    {
    }
 
    void m2()
    {
    }
}";
 
            var solution = GetSolution(code);
            var symbol = (INamedTypeSymbol)(await GetSymbolsAsync(solution, "C")).First();
            var member = symbol.GetMembers("m").First();
            var editor = SymbolEditor.Create(solution);
 
            var newSymbol = (INamedTypeSymbol)await editor.EditOneDeclarationAsync(symbol, member, (e, d) => e.AddMember(d, e.Generator.MethodDeclaration("m2")));
            Assert.Equal(1, newSymbol.GetMembers("m").Length);
 
            var actual = await GetActualAsync(editor.GetChangedDocuments().First());
 
            Assert.Equal(expected, actual);
        }
 
        [Fact]
        public async Task TestChangeLogicalIdentityReturnsCorrectSymbol_OneDeclaration()
        {
            // proves that APIs return the correct new symbol even after a change that changes the symbol's logical identity.
            var code =
@"class C
{
}";
 
            var expected =
@"class X
{
}";
 
            var solution = GetSolution(code);
            var symbol = (await GetSymbolsAsync(solution, "C")).First();
            var editor = SymbolEditor.Create(solution);
 
            var newSymbol = (INamedTypeSymbol)await editor.EditOneDeclarationAsync(symbol, (e, d) => e.SetName(d, "X"));
            Assert.Equal("X", newSymbol.Name);
 
            // original symbols cannot be rebound after identity change.
            var reboundSymbol = await editor.GetCurrentSymbolAsync(symbol);
            Assert.Null(reboundSymbol);
 
            var actual = await GetActualAsync(editor.GetChangedDocuments().First());
 
            Assert.Equal(expected, actual);
        }
 
        [Fact]
        public async Task TestChangeLogicalIdentityReturnsCorrectSymbol_AllDeclarations()
        {
            // proves that APIs return the correct new symbol even after a change that changes the symbol's logical identity.
            var code =
@"partial class C
{
}
 
partial class C
{
}";
 
            var expected =
@"partial class X
{
}
 
partial class X
{
}";
            var solution = GetSolution(code);
            var symbol = (await GetSymbolsAsync(solution, "C")).First();
            var editor = SymbolEditor.Create(solution);
 
            var newSymbol = (INamedTypeSymbol)await editor.EditAllDeclarationsAsync(symbol, (e, d) => e.SetName(d, "X"));
            Assert.Equal("X", newSymbol.Name);
 
            // original symbols cannot be rebound after identity change.
            var reboundSymbol = await editor.GetCurrentSymbolAsync(symbol);
            Assert.Null(reboundSymbol);
 
            var actual = await GetActualAsync(editor.GetChangedDocuments().First());
 
            Assert.Equal(expected, actual);
        }
 
        [Fact]
        public async Task TestRemovedDeclarationReturnsNull()
        {
            var code =
@"class C
{
}";
 
            var expected =
@"";
 
            var solution = GetSolution(code);
            var symbol = (await GetSymbolsAsync(solution, "C")).First();
            var editor = SymbolEditor.Create(solution);
 
            var newSymbol = (INamedTypeSymbol)await editor.EditOneDeclarationAsync(symbol, (e, d) => e.RemoveNode(d));
            Assert.Null(newSymbol);
 
            var actual = await GetActualAsync(editor.GetChangedDocuments().First());
 
            Assert.Equal(expected, actual);
        }
 
        [Fact]
        public async Task TestRemovedOneOfManyDeclarationsReturnsChangedSymbol()
        {
            var code =
@"partial class C
{
}
 
partial class C
{
}";
 
            var expected =
@"
partial class C
{
}";
 
            var solution = GetSolution(code);
            var symbol = (await GetSymbolsAsync(solution, "C")).First();
            var editor = SymbolEditor.Create(solution);
 
            var newSymbol = (INamedTypeSymbol)await editor.EditOneDeclarationAsync(symbol, (e, d) => e.RemoveNode(d));
            Assert.NotNull(newSymbol);
            Assert.Equal("C", newSymbol.Name);
 
            var actual = await GetActualAsync(editor.GetChangedDocuments().First());
 
            Assert.Equal(expected, actual);
        }
 
        [Fact]
        public async Task TestRemoveAllOfManyDeclarationsReturnsNull()
        {
            var code =
@"partial class C
{
}
 
partial class C
{
}";
 
            var expected =
@"
";
 
            var solution = GetSolution(code);
            var symbol = (await GetSymbolsAsync(solution, "C")).First();
            var editor = SymbolEditor.Create(solution);
 
            var newSymbol = (INamedTypeSymbol)await editor.EditAllDeclarationsAsync(symbol, (e, d) => e.RemoveNode(d));
            Assert.Null(newSymbol);
 
            var actual = await GetActualAsync(editor.GetChangedDocuments().First());
 
            Assert.Equal(expected, actual);
        }
 
        [Fact]
        public async Task TestRemoveFieldFromMultiFieldDeclaration()
        {
            var code =
@"class C
{
    public int X, Y;
}";
 
            var expected =
@"class C
{
    public int Y;
}";
 
            var expected2 =
@"class C
{
}";
 
            var solution = GetSolution(code);
            var symbol = (INamedTypeSymbol)(await GetSymbolsAsync(solution, "C")).First();
            var symbolX = symbol.GetMembers("X").First();
            var symbolY = symbol.GetMembers("Y").First();
 
            var editor = SymbolEditor.Create(solution);
 
            // remove X -- should remove only part of the field declaration.
            var newSymbolX = (INamedTypeSymbol)await editor.EditOneDeclarationAsync(symbolX, (e, d) => e.RemoveNode(d));
            Assert.Null(newSymbolX);
 
            var actual = await GetActualAsync(editor.GetChangedDocuments().First());
            Assert.Equal(expected, actual);
 
            // now remove Y -- should remove entire remaining field declaration
            var newSymbolY = (INamedTypeSymbol)await editor.EditOneDeclarationAsync(symbolY, (e, d) => e.RemoveNode(d));
            Assert.Null(newSymbolY);
 
            actual = await GetActualAsync(editor.GetChangedDocuments().First());
            Assert.Equal(expected2, actual);
        }
 
        [Fact]
        public async Task TestSetBaseType_ExistingBase()
        {
            var code =
@"class C : B
{
}
 
class A
{
}
 
class B
{
}";
 
            var expected =
@"class C : A
{
}
 
class A
{
}
 
class B
{
}";
 
            var solution = GetSolution(code);
            var symbol = (INamedTypeSymbol)(await GetSymbolsAsync(solution, "C")).First();
 
            var editor = SymbolEditor.Create(solution);
 
            // set base to A
            var newSymbolC = await editor.SetBaseTypeAsync(symbol, g => g.IdentifierName("A"));
 
            var actual = await GetActualAsync(editor.GetChangedDocuments().First());
            Assert.Equal(expected, actual);
        }
 
        [Fact]
        public async Task TestSetBaseType_ExistingInterface()
        {
            var code =
@"class C : I
{
}
 
class A
{
}
 
interface I
{
}";
 
            var expected =
@"class C : A, I
{
}
 
class A
{
}
 
interface I
{
}";
 
            var solution = GetSolution(code);
            var symbol = (INamedTypeSymbol)(await GetSymbolsAsync(solution, "C")).First();
 
            var editor = SymbolEditor.Create(solution);
 
            // set base to A
            var newSymbolC = await editor.SetBaseTypeAsync(symbol, g => g.IdentifierName("A"));
 
            var actual = await GetActualAsync(editor.GetChangedDocuments().First());
            Assert.Equal(expected, actual);
        }
 
        [Fact]
        public async Task TestSetBaseType_NoBaseOrInterface()
        {
            var code =
@"class C
{
}
 
class A
{
}";
 
            var expected =
@"class C : A
{
}
 
class A
{
}";
 
            var solution = GetSolution(code);
            var symbol = (INamedTypeSymbol)(await GetSymbolsAsync(solution, "C")).First();
 
            var editor = SymbolEditor.Create(solution);
 
            // set base to A
            var newSymbolC = await editor.SetBaseTypeAsync(symbol, g => g.IdentifierName("A"));
 
            var actual = await GetActualAsync(editor.GetChangedDocuments().First());
            Assert.Equal(expected, actual);
        }
 
        [Fact]
        public async Task TestSetBaseType_UnknownBase()
        {
            var code =
@"class C : X
{
}
 
class A
{
}";
 
            var expected =
@"class C : A
{
}
 
class A
{
}";
 
            var solution = GetSolution(code);
            var symbol = (INamedTypeSymbol)(await GetSymbolsAsync(solution, "C")).First();
 
            var editor = SymbolEditor.Create(solution);
 
            // set base to A
            var newSymbolC = editor.SetBaseTypeAsync(symbol, g => g.IdentifierName("A"));
 
            var actual = await GetActualAsync(editor.GetChangedDocuments().First());
            Assert.Equal(expected, actual);
        }
 
        [Fact]
        public async Task TestSetBaseType_Null_ExistingBase()
        {
            var code =
@"class C : A
{
}
 
class A
{
}";
 
            var expected =
@"class C
{
}
 
class A
{
}";
 
            var solution = GetSolution(code);
            var symbol = (INamedTypeSymbol)(await GetSymbolsAsync(solution, "C")).First();
 
            var editor = SymbolEditor.Create(solution);
 
            // set base to null
            var newSymbolC = await editor.SetBaseTypeAsync(symbol, g => null);
 
            var actual = await GetActualAsync(editor.GetChangedDocuments().First());
            Assert.Equal(expected, actual);
        }
 
        [Fact]
        public async Task TestSetBaseType_Null_ExistingBaseAndInterface()
        {
            var code =
@"class C : A, I
{
}
 
class A
{
}
 
interface I
{
}";
 
            var expected =
@"class C : I
{
}
 
class A
{
}
 
interface I
{
}";
 
            var solution = GetSolution(code);
            var symbol = (INamedTypeSymbol)(await GetSymbolsAsync(solution, "C")).First();
 
            var editor = SymbolEditor.Create(solution);
 
            // set base to null
            var newSymbolC = await editor.SetBaseTypeAsync(symbol, g => null);
 
            var actual = await GetActualAsync(editor.GetChangedDocuments().First());
            Assert.Equal(expected, actual);
        }
 
        [Fact]
        public async Task TestSetBaseType_Null_ExistingInterface()
        {
            var code =
@"class C : I
{
}
 
interface I
{
}";
 
            var expected =
@"class C : I
{
}
 
interface I
{
}";
 
            var solution = GetSolution(code);
            var symbol = (INamedTypeSymbol)(await GetSymbolsAsync(solution, "C")).First();
 
            var editor = SymbolEditor.Create(solution);
 
            // set base to null
            var newSymbolC = await editor.SetBaseTypeAsync(symbol, g => null);
 
            var actual = await GetActualAsync(editor.GetChangedDocuments().First());
            Assert.Equal(expected, actual);
        }
 
        [Fact]
        public async Task TestSetBaseType_Null_UnknownBase()
        {
            var code =
@"class C : X
{
}";
 
            var expected =
@"class C
{
}";
 
            var solution = GetSolution(code);
            var symbol = (INamedTypeSymbol)(await GetSymbolsAsync(solution, "C")).First();
 
            var editor = SymbolEditor.Create(solution);
 
            // set base to null
            var newSymbolC = await editor.SetBaseTypeAsync(symbol, g => null);
 
            var actual = await GetActualAsync(editor.GetChangedDocuments().First());
            Assert.Equal(expected, actual);
        }
 
        [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/2650")]
        public async Task TestEditExplicitInterfaceIndexer()
        {
            var code =
@"public interface I
{
    int this[int item] { get; }
}
 
public class C  : I
{
    int I.this[int item]
    {
        get
        {
            return item;
        }
    }
}";
 
            var solution = GetSolution(code);
            var typeC = (INamedTypeSymbol)(await GetSymbolsAsync(solution, "C")).First();
            var property = typeC.GetMembers().First(m => m.Kind == SymbolKind.Property);
 
            var editor = SymbolEditor.Create(solution);
 
            var newProperty = editor.EditOneDeclarationAsync(property, (e, d) =>
            {
                // nothing
            });
 
            var typeI = (INamedTypeSymbol)(await GetSymbolsAsync(solution, "I")).First();
            var iproperty = typeI.GetMembers().First(m => m.Kind == SymbolKind.Property);
 
            var newIProperty = editor.EditOneDeclarationAsync(iproperty, (e, d) =>
            {
                // nothing;
            });
        }
    }
}