Reputation: 4806
I'm building an analyzer for C# code which generates errors when a string literal is used instead of a const string for certain arguments for certain functions. Ie.
class MyClass
{
private void MyMethod(IWriter writer)
{
writer.WriteInteger("NamedValue", 4);
}
}
Should become:
class MyClass
{
private const string IoNamedValueKey = "NamedValue";
private void MyMethod(IWriter writer)
{
writer.WriteInteger(IoNamedValueKey , 4);
}
}
I've got the bit working where it displays the error, but I want to provide a CodeFixProvider as well. I've run into two problems:
private const string IoNamedValueKey = "NamedValue";
statement, ideally just above the offending method.I'm not entirely sure the template approach for the CodeFixProvider uses the appropriate overloads for my purposes (it merely replaces type names with upper case variants), so what would be the best way forward from within the RegisterCodeFixesAsync method?
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
// ... now what?
}
According to roslynquoter the required node can be constructed as below, but I'm still somewhat at a loss about how to inject it into the context.
CompilationUnit()
.WithMembers(
SingletonList<MemberDeclarationSyntax>(
FieldDeclaration(
VariableDeclaration(
PredefinedType(
Token(SyntaxKind.StringKeyword)))
.WithVariables(
SingletonSeparatedList<VariableDeclaratorSyntax>(
VariableDeclarator(
Identifier("IoNamedValueKey"))
.WithInitializer(
EqualsValueClause(
LiteralExpression(
SyntaxKind.StringLiteralExpression,
Literal("NamedValue")))))))
.WithModifiers(
TokenList(
new []{
Token(SyntaxKind.PrivateKeyword),
Token(SyntaxKind.ConstKeyword)}))))
.NormalizeWhitespace()
Upvotes: 1
Views: 797
Reputation: 3005
You should register a CodeAction
that introduces the changed document through the context
. For
SyntaxNodes
- you can use use CSharp
SyntaxFactoryHere is an example scratch of what your code might look like (updated):
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var diagnostic = context.Diagnostics.First();
var diagnosticSpan = diagnostic.Location.SourceSpan;
var argument = root.FindNode(diagnosticSpan);
if (!IsBadStringLiteralArgument(argument))
{
return;
}
// Register a code action that will invoke the fix.
context.RegisterCodeFix(
CodeAction.Create(
title: title,
createChangedDocument: (ct) => InlineConstField(context.Document, root, argument, ct),
equivalenceKey: title),
diagnostic);
}
private async Task<Document> InlineConstField(Document document, SyntaxNode root, SyntaxNode argument, CancellationToken cancellationToken)
{
var stringLiteral = (argument as ArgumentSyntax).Expression as LiteralExpressionSyntax;
string suggestdName = this.GetSuggestedName(stringLiteral);
var containingMember = argument.FirstAncestorOrSelf<MemberDeclarationSyntax>();
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var containingMemberSymbol = semanticModel.GetDeclaredSymbol(containingMember);
var takenNames = containingMemberSymbol.ContainingType.MemberNames;
string uniqueName = this.GetUniqueName(suggestdName, takenNames);
FieldDeclarationSyntax constField = CreateConstFieldDeclaration(uniqueName, stringLiteral).WithAdditionalAnnotations(Formatter.Annotation);
var newRoot = root.ReplaceNode(containingMember, new[] { constField, containingMember });
newRoot = Formatter.Format(newRoot, Formatter.Annotation, document.Project.Solution.Workspace);
return document.WithSyntaxRoot(newRoot);
}
private FieldDeclarationSyntax CreateConstFieldDeclaration(string uniqueName, LiteralExpressionSyntax stringLiteral)
{
return SyntaxFactory.FieldDeclaration(
SyntaxFactory.List<AttributeListSyntax>(),
SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.ConstKeyword)),
SyntaxFactory.VariableDeclaration(
SyntaxFactory.ParseTypeName("string"),
SyntaxFactory.SingletonSeparatedList(
SyntaxFactory.VariableDeclarator(
SyntaxFactory.Identifier(uniqueName),
argumentList: null,
initializer: SyntaxFactory.EqualsValueClause(stringLiteral)))));
}
Upvotes: 3