Working on VSDiagnostics, I implemented a tool that adds the default access modifiers to C# code. An example use would be starting with this:
static class Foo { }
And ending with this:
internal static class Foo { }
This is the analyzer:
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(AnalyzeSymbol,
SyntaxKind.ClassDeclaration,
SyntaxKind.ConstructorDeclaration,
SyntaxKind.DelegateDeclaration,
SyntaxKind.EnumDeclaration,
SyntaxKind.EventDeclaration,
SyntaxKind.EventFieldDeclaration,
SyntaxKind.FieldDeclaration,
SyntaxKind.IndexerDeclaration,
SyntaxKind.InterfaceDeclaration,
SyntaxKind.MethodDeclaration,
SyntaxKind.PropertyDeclaration,
SyntaxKind.StructDeclaration);
}
private void AnalyzeSymbol(SyntaxNodeAnalysisContext context)
{
if (context.Node is ClassDeclarationSyntax)
{
var declarationExpression = (ClassDeclarationSyntax) context.Node;
if (!declarationExpression.Modifiers.Any(m => _accessModifierKinds.Contains(m.Kind())))
{
var accessibility = context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility;
context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(),
accessibility.ToString().ToLower()));
}
}
if (context.Node is StructDeclarationSyntax)
{
var declarationExpression = (StructDeclarationSyntax)context.Node;
if (!declarationExpression.Modifiers.Any(m => _accessModifierKinds.Contains(m.Kind())))
{
var accessibility = context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility;
context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(),
accessibility.ToString().ToLower()));
}
}
if (context.Node is EnumDeclarationSyntax)
{
var declarationExpression = (EnumDeclarationSyntax)context.Node;
if (!declarationExpression.Modifiers.Any(m => _accessModifierKinds.Contains(m.Kind())))
{
var accessibility = context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility;
context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(),
accessibility.ToString().ToLower()));
}
}
if (context.Node is DelegateDeclarationSyntax)
{
var declarationExpression = (DelegateDeclarationSyntax)context.Node;
if (!declarationExpression.Modifiers.Any(m => _accessModifierKinds.Contains(m.Kind())))
{
var accessibility = context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility;
context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(),
accessibility.ToString().ToLower()));
}
}
if (context.Node is InterfaceDeclarationSyntax)
{
var declarationExpression = (InterfaceDeclarationSyntax)context.Node;
if (!declarationExpression.Modifiers.Any(m => _accessModifierKinds.Contains(m.Kind())))
{
var accessibility = context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility;
context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(),
accessibility.ToString().ToLower()));
}
}
if (context.Node is FieldDeclarationSyntax)
{
var declarationExpression = (FieldDeclarationSyntax)context.Node;
if (!declarationExpression.Modifiers.Any(m => _accessModifierKinds.Contains(m.Kind())))
{
context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(),
"private"));
}
}
if (context.Node is PropertyDeclarationSyntax)
{
var declarationExpression = (PropertyDeclarationSyntax)context.Node;
if (!declarationExpression.Modifiers.Any(m => _accessModifierKinds.Contains(m.Kind())))
{
var accessibility = context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility;
context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(),
accessibility.ToString().ToLower()));
}
}
if (context.Node is MethodDeclarationSyntax)
{
var declarationExpression = (MethodDeclarationSyntax)context.Node;
if (!declarationExpression.Modifiers.Any(m => _accessModifierKinds.Contains(m.Kind())))
{
var accessibility = context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility;
context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(),
accessibility.ToString().ToLower()));
}
}
if (context.Node is ConstructorDeclarationSyntax)
{
var declarationExpression = (ConstructorDeclarationSyntax)context.Node;
if (!declarationExpression.Modifiers.Any(m => _accessModifierKinds.Contains(m.Kind()) || m.Kind() == SyntaxKind.StaticKeyword))
{
var accessibility = context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility;
context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(),
accessibility.ToString().ToLower()));
}
}
if (context.Node is EventFieldDeclarationSyntax)
{
var declarationExpression = (EventFieldDeclarationSyntax)context.Node;
if (!declarationExpression.Modifiers.Any(m => _accessModifierKinds.Contains(m.Kind())))
{
context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(),
"private"));
}
}
if (context.Node is EventDeclarationSyntax)
{
var declarationExpression = (EventDeclarationSyntax)context.Node;
if (!declarationExpression.Modifiers.Any(m => _accessModifierKinds.Contains(m.Kind())))
{
var accessibility = context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility;
context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(),
accessibility.ToString().ToLower()));
}
}
if (context.Node is IndexerDeclarationSyntax)
{
var declarationExpression = (IndexerDeclarationSyntax)context.Node;
if (!declarationExpression.Modifiers.Any(m => _accessModifierKinds.Contains(m.Kind())))
{
var accessibility = context.SemanticModel.GetDeclaredSymbol(declarationExpression).DeclaredAccessibility;
context.ReportDiagnostic(Diagnostic.Create(Rule, declarationExpression.GetLocation(),
accessibility.ToString().ToLower()));
}
}
}
private readonly SyntaxKind[] _accessModifierKinds =
{
SyntaxKind.PublicKeyword,
SyntaxKind.ProtectedKeyword,
SyntaxKind.InternalKeyword,
SyntaxKind.PrivateKeyword
};
I cannot get the current access modifier for EventFieldDeclaration
s and FieldDeclaration
s because the type containing the access modifier is a child of this type.
This is the code fix:
public 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 statement = root.FindNode(diagnosticSpan);
var semanticModel = context.Document.GetSemanticModelAsync().Result;
var symbol = semanticModel.GetDeclaredSymbol(statement);
var accessibility = symbol?.DeclaredAccessibility ?? Accessibility.Private;
context.RegisterCodeFix(CodeAction.Create(VSDiagnosticsResources.ExplicitAccessModifiersCodeFixTitle, x => AddModifier(context.Document, root, statement, accessibility), nameof(ExplicitAccessModifiersAnalyzer)), diagnostic);
}
private Task<Solution> AddModifier(Document document, SyntaxNode root, SyntaxNode statement, Accessibility accessibility)
{
SyntaxNode newStatement = null;
var accessModifierTokens = SyntaxFactory.TokenList(AccessModifiers(accessibility));
if (statement is ClassDeclarationSyntax)
{
var classExpression = (ClassDeclarationSyntax) statement;
var newClass = classExpression.WithModifiers(classExpression.Modifiers.AddRange(accessModifierTokens));
newStatement = statement.ReplaceNode(statement, newClass);
}
if (statement is StructDeclarationSyntax)
{
var structExpression = (StructDeclarationSyntax)statement;
var newStruct = structExpression.WithModifiers(structExpression.Modifiers.AddRange(accessModifierTokens));
newStatement = statement.ReplaceNode(statement, newStruct);
}
if (statement is EnumDeclarationSyntax)
{
var enumExpression = (EnumDeclarationSyntax)statement;
var newEnum = enumExpression.WithModifiers(enumExpression.Modifiers.AddRange(accessModifierTokens));
newStatement = statement.ReplaceNode(statement, newEnum);
}
if (statement is DelegateDeclarationSyntax)
{
var delegateExpression = (DelegateDeclarationSyntax)statement;
var newDelegate = delegateExpression.WithModifiers(delegateExpression.Modifiers.AddRange(accessModifierTokens));
newStatement = statement.ReplaceNode(statement, newDelegate);
}
if (statement is InterfaceDeclarationSyntax)
{
var interfaceExpression = (InterfaceDeclarationSyntax)statement;
var newInterface = interfaceExpression.WithModifiers(interfaceExpression.Modifiers.AddRange(accessModifierTokens));
newStatement = statement.ReplaceNode(statement, newInterface);
}
if (statement is FieldDeclarationSyntax)
{
var fieldExpression = (FieldDeclarationSyntax)statement;
var newStruct = fieldExpression.WithModifiers(fieldExpression.Modifiers.AddRange(accessModifierTokens));
newStatement = statement.ReplaceNode(statement, newStruct);
}
if (statement is PropertyDeclarationSyntax)
{
var propertyExpression = (PropertyDeclarationSyntax)statement;
var newProperty = propertyExpression.WithModifiers(propertyExpression.Modifiers.AddRange(accessModifierTokens));
newStatement = statement.ReplaceNode(statement, newProperty);
}
if (statement is MethodDeclarationSyntax)
{
var methodExpression = (MethodDeclarationSyntax)statement;
var newMethod = methodExpression.WithModifiers(methodExpression.Modifiers.AddRange(accessModifierTokens));
newStatement = statement.ReplaceNode(statement, newMethod);
}
if (statement is ConstructorDeclarationSyntax)
{
var constructorExpression = (ConstructorDeclarationSyntax)statement;
var newConstructor = constructorExpression.WithModifiers(constructorExpression.Modifiers.AddRange(accessModifierTokens));
newStatement = statement.ReplaceNode(statement, newConstructor);
}
if (statement is EventFieldDeclarationSyntax)
{
var eventFieldExpression = (EventFieldDeclarationSyntax)statement;
var newEventField = eventFieldExpression.WithModifiers(eventFieldExpression.Modifiers.AddRange(accessModifierTokens));
newStatement = statement.ReplaceNode(statement, newEventField);
}
if (statement is EventDeclarationSyntax)
{
var eventExpression = (EventDeclarationSyntax)statement;
var newEvent = eventExpression.WithModifiers(eventExpression.Modifiers.AddRange(accessModifierTokens));
newStatement = statement.ReplaceNode(statement, newEvent);
}
if (statement is IndexerDeclarationSyntax)
{
var indexerExpression = (IndexerDeclarationSyntax)statement;
var newIndexer = indexerExpression.WithModifiers(indexerExpression.Modifiers.AddRange(accessModifierTokens));
newStatement = statement.ReplaceNode(statement, newIndexer);
}
var newRoot = newStatement == null ? root : root.ReplaceNode(statement, newStatement);
return Task.FromResult(document.WithSyntaxRoot(newRoot).Project.Solution);
}
private SyntaxToken[] AccessModifiers(Accessibility accessibility)
{
switch (accessibility)
{
case Accessibility.Private:
return new[] { SyntaxFactory.Token(SyntaxKind.PrivateKeyword) };
case Accessibility.ProtectedAndInternal:
return new[] { SyntaxFactory.Token(SyntaxKind.ProtectedKeyword), SyntaxFactory.Token(SyntaxKind.InternalKeyword) };
case Accessibility.Protected:
return new[] { SyntaxFactory.Token(SyntaxKind.ProtectedKeyword) };
case Accessibility.Internal:
return new[] { SyntaxFactory.Token(SyntaxKind.InternalKeyword) };
case Accessibility.Public:
return new[] { SyntaxFactory.Token(SyntaxKind.PublicKeyword) };
}
return null; // this cannot be reached
}
And here is a subset of the 68 unit tests:
[TestMethod]
public void ExplicitAccessModifiers_ClassDeclaration_InvokesWarning()
{
var original = @"
namespace ConsoleApplication1
{
class MyClass
{
}
}";
var result = @"
namespace ConsoleApplication1
{
internal class MyClass
{
}
}";
VerifyDiagnostic(original, string.Format(ExplicitAccessModifiersAnalyzer.Rule.MessageFormat.ToString(), "internal"));
VerifyFix(original, result);
}
[TestMethod]
public void ExplicitAccessModifiers_ClassDeclaration_ContainsNonAccessModifier_InvokesWarning()
{
var original = @"
namespace ConsoleApplication1
{
static class MyClass
{
}
}";
var result = @"
namespace ConsoleApplication1
{
static internal class MyClass
{
}
}";
VerifyDiagnostic(original, string.Format(ExplicitAccessModifiersAnalyzer.Rule.MessageFormat.ToString(), "internal"));
VerifyFix(original, result);
}
[TestMethod]
public void ExplicitAccessModifiers_ClassDeclaration_ContainsAccessModifier_DoesNotInvokeWarning()
{
var original = @"
namespace ConsoleApplication1
{
public class MyClass
{
}
}";
VerifyDiagnostic(original);
}
[TestMethod]
public void ExplicitAccessModifiers_ClassDeclaration_OnlyChangesAccessModifiers_InvokesWarning()
{
var original = @"
using System;
namespace ConsoleApplication1
{
[Obsolete]
class MyClass
{
public void Method() { }
}
}";
var result = @"
using System;
namespace ConsoleApplication1
{
[Obsolete]
internal class MyClass
{
public void Method() { }
}
}";
VerifyDiagnostic(original, string.Format(ExplicitAccessModifiersAnalyzer.Rule.MessageFormat.ToString(), "internal"));
VerifyFix(original, result);
}
I appreciate any and all feedback.