Recently there was a question on StackOverflow asking how to remove params from a method parameter list. There is a lot of extra information in that question, so I thought I would distill it down and explain some things in more detail.

To start, we need an analyzer that raises a diagnostic on the parmas keyword. We'll start by just raising it on every paramas keyword, as we really care more about the code fix in this case.

public override void Initialize(AnalysisContext context)
{
    context.RegisterSyntaxNodeAction((syntaxContext) =>
    {
        var node = syntaxContext.Node as ParameterListSyntax;
        if (node.Parameters.LastOrDefault().Modifiers.Any(x => x.IsKind(SyntaxKind.ParamsKeyword)))
        {
            syntaxContext.ReportDiagnostic(Diagnostic.Create(Rule, node.GetLocation()));
        }
    }, SyntaxKind.ParameterList);
}

In this case, we are registering for all ParameterLists and checking if that last keyword has a ParamsKeyword modifier. The params keyword is considered a modifier, similar to public, private, static, etc ...

Now that we have an analyzer that can detect params, we want to remove them. To start, we will simply register our code fix:

var diagnostic = context.Diagnostics.First();
context.RegisterCodeFix(CodeAction.Create("Remove 'params' modifier", async token => {

Next, we are going to get the document and syntax root and use the root to find the parameters list that raised the diagnostic.

var document = context.Document;
var root = await document.GetSyntaxRootAsync(token);

var allParmeters = root.FindNode(diagnostic.Location.SourceSpan, false) as ParameterListSyntax;

Since we are looking for params and they have to be the last parameter, we are just looking at the last parameter in the parameter list.

var lastParameter = allParmeters.Parameters.LastOrDefault();

Now that we have the correct parameter, we are going to get all modifiers off the paramter except the params keyword and place them in a new SyntaxTokenList of modifiers.

// Keep all modifiers except the params
var newModifiers = lastParameter.Modifiers.Where(m => !m.IsKind(SyntaxKind.ParamsKeyword));
var syntaxModifiers = SyntaxTokenList.Create(new SyntaxToken());
syntaxModifiers.AddRange(newModifiers);

Since all SyntaxNodes are immutable, we need to get a new SytnaxNode with the correct modifiers. The Roslyn API provides a WithModifiers extension method that does just that for us.

var updatedParameterNode = lastParameter.WithModifiers(syntaxModifiers);

Finally we will replace the old node with the new node in the Syntax tree and return that.

var newDoc = document.WithSyntaxRoot(root.ReplaceNode(lastParameter, updatedParameterNode));
return newDoc;

In the end, the full code fix looks like this:

public async override Task RegisterCodeFixesAsync(CodeFixContext context)
{

    var diagnostic = context.Diagnostics.First();
    context.RegisterCodeFix(CodeAction.Create("Remove 'params' modifier", async token =>
    {

        var document = context.Document;
        var root = await document.GetSyntaxRootAsync(token);

        var allParmeters = root.FindNode(diagnostic.Location.SourceSpan, false) as ParameterListSyntax;

        var lastParameter = allParmeters.Parameters.LastOrDefault();

        // Keep all modifiers except the params
        var newModifiers = lastParameter.Modifiers.Where(m => !m.IsKind(SyntaxKind.ParamsKeyword));
        var syntaxModifiers = SyntaxTokenList.Create(new SyntaxToken());
        syntaxModifiers.AddRange(newModifiers);

        var updatedParameterNode = lastParameter.WithModifiers(syntaxModifiers);

        var newDoc = document.WithSyntaxRoot(root.ReplaceNode(lastParameter, updatedParameterNode));
        return newDoc;
    }, "KEY"), diagnostic);
}

And there you have it. A simple code fix that removes the params modifier from a parameter list. As you can see, this code is pretty straightforward, and the concepts can be easily extended to remove any modifiers that are present on a SyntaxNode.