One of the most basic actions a Visual Studio code fix can perform is renaming of a symbol. To demonstrate how to do this, I will walk through how the StyleCop Analyzer SA1309 (Field Names Must Not Begin With Underscore) performs its fix.

As the name implies, this diagnostic is raised any time there is a field that begins with an underscore. So, for example, the following code would raise a diagnostic, because of the field name _bar:

public class Foo
{
    public string _bar = ""baz"";
}

In order to fix this diagnostic, we will loop over all of the Diagnostics sent in the RegisterCodeFixesAsync method and compute a new name for the token.

public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
    var document = context.Document;
    var root = await document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);

    foreach (var diagnostic in context.Diagnostics)
    {
        if (!diagnostic.Id.Equals(SA1309FieldNamesMustNotBeginWithUnderscore.DiagnosticId))
        {
            continue;
        }

        var token = root.FindToken(diagnostic.Location.SourceSpan.Start);
        if (token.IsMissing)
        {
            continue;
        }

        if (!string.IsNullOrEmpty(token.ValueText))
        {
            var newName = token.ValueText.TrimStart(new[] { '_' });

            if (string.IsNullOrEmpty(newName))
            {
                // The variable consisted of only underscores. In this case we cannot
                // generate a valid variable name and thus will not offer a code fix.
                continue;
            }

            context.RegisterCodeFix(CodeAction.Create(string.Format(NamingResources.SA1309CodeFix, newName), 
                 cancellationToken => RenameHelper.RenameSymbolAsync(document, root, token, newName, cancellationToken), 
                 equivalenceKey: nameof(SA1309CodeFixProvider)), diagnostic);
        }
    }
}

The most important bits here are the part where we calculate the new name and the part where we register the code fix. So in the computation of the new name, we simply remove the underscores from the start of the name. The only odd case we need to account for is if the variable name is only underscores.

var newName = token.ValueText.TrimStart(new[] { '_' });

if (string.IsNullOrEmpty(newName))
{
    // The variable consisted of only underscores. In this case we cannot
    // generate a valid variable name and thus will not offer a code fix.
    continue;
}

The other interesting code in here is the RenameHelper.RenameSymbolAsnc call, which is what does the actual renaming of the symbol. This code can be found in the RenameHelpers class in the StyleCop Analyzers project on GitHub. Let's dig into that code a bit.

public static async Task<Solution> RenameSymbolAsync(Document document, SyntaxNode root, SyntaxToken declarationToken, string newName, CancellationToken cancellationToken)
{
    var annotatedRoot = root.ReplaceToken(declarationToken, declarationToken.WithAdditionalAnnotations(RenameAnnotation.Create()));
    var annotatedSolution = document.Project.Solution.WithDocumentSyntaxRoot(document.Id, annotatedRoot);
    var annotatedDocument = annotatedSolution.GetDocument(document.Id);

    annotatedRoot = await annotatedDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
    var annotatedToken = annotatedRoot.FindToken(declarationToken.SpanStart);

    var semanticModel = await annotatedDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
    var symbol = semanticModel?.GetDeclaredSymbol(annotatedToken.Parent, cancellationToken);

    var newSolution = await Renamer.RenameSymbolAsync(annotatedSolution, symbol, newName, null, cancellationToken).ConfigureAwait(false);
    return newSolution;
}

Let's break this method down to see what it is doing. Let's start with all of the annotation code.

var annotatedRoot = root.ReplaceToken(declarationToken, declarationToken.WithAdditionalAnnotations(RenameAnnotation.Create()));
var annotatedSolution = document.Project.Solution.WithDocumentSyntaxRoot(document.Id, annotatedRoot);
var annotatedDocument = annotatedSolution.GetDocument(document.Id);

annotatedRoot = await annotatedDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var annotatedToken = annotatedRoot.FindToken(declarationToken.SpanStart);

Essentially what this code is doing is getting new objects (remember SyntaxNodes, Documents, Solutions, etc... are immutable) with annotations to indicate that we need to do a rename. This is akin to starting a rename within Visual Studio and it highlighting all of the instances of the symbol you are renaming. Next, we need to get the semantic model and the symbol from the annotated objects, so we can use the symbol in the rename helper.

var semanticModel = await annotatedDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var symbol = semanticModel?.GetDeclaredSymbol(annotatedToken.Parent, cancellationToken);

Finally, Roslyn provides a RenameSymbolAsync that will perform the rename given the annotated solution, symbol, and new name.

var newSolution = await Renamer.RenameSymbolAsync(annotatedSolution, symbol, newName, null, cancellationToken).ConfigureAwait(false);
return newSolution;

And there you have it, the code to do a rename of a symbol inside your code fix. As you can see, the RenameHelpers and RenameSymbolAsync helper methods wrap a lot of the hard work that needs to be done when renaming a symbol and you can focus on figuring out the name for your new symbols.