Endlich hatte ich etwas Zeit mit Roslyn zu experimentieren und sah aus, als hätte ich eine Lösung mit einem Analysator gefunden. Das ist meine Lösung.
Das Attribut, das Verfahren zu markieren, die in der Unterklasse außer Kraft gesetzt werden muss:
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
public sealed class RequireBaseMethodCallAttribute : Attribute
{
public RequireBaseMethodCallAttribute() { }
}
Der Analysator:
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class RequiredBaseMethodCallAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "RequireBaseMethodCall";
// You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat.
// See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/Localizing%20Analyzers.md for more on localization
private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.AnalyzerTitle), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.AnalyzerDescription), Resources.ResourceManager, typeof(Resources));
private const string Category = "Usage";
private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }
public override void Initialize(AnalysisContext context)
{
context.RegisterCompilationStartAction(AnalyzeMethodForBaseCall);
}
private static void AnalyzeMethodForBaseCall(CompilationStartAnalysisContext compilationStartContext)
{
compilationStartContext.RegisterSyntaxNodeAction(AnalyzeMethodDeclaration, SyntaxKind.MethodDeclaration);
}
private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context)
{
var mds = context.Node as MethodDeclarationSyntax;
if (mds == null)
{
return;
}
IMethodSymbol symbol = context.SemanticModel.GetDeclaredSymbol(mds) as IMethodSymbol;
if (symbol == null)
{
return;
}
if (!symbol.IsOverride)
{
return;
}
if (symbol.OverriddenMethod == null)
{
return;
}
var overridenMethod = symbol.OverriddenMethod;
var attrs = overridenMethod.GetAttributes();
if (!attrs.Any(ad => ad.AttributeClass.MetadataName.ToUpperInvariant()
== typeof(RequireBaseMethodCallAttribute).Name.ToUpperInvariant()))
{
return;
}
var overridenMethodName = overridenMethod.Name.ToString();
string methodName = overridenMethodName;
var invocations = mds.DescendantNodes().OfType<MemberAccessExpressionSyntax>().ToList();
foreach (var inv in invocations)
{
var expr = inv.Expression;
if ((SyntaxKind)expr.RawKind == SyntaxKind.BaseExpression)
{
var memberAccessExpr = expr.Parent as MemberAccessExpressionSyntax;
if (memberAccessExpr == null)
{
continue;
}
// compare exprSymbol and overridenMethod
var exprMethodName = memberAccessExpr.Name.ToString();
if (exprMethodName != overridenMethodName)
{
continue;
}
var invokationExpr = memberAccessExpr.Parent as InvocationExpressionSyntax;
if (invokationExpr == null)
{
continue;
}
var exprMethodArgs = invokationExpr.ArgumentList.Arguments.ToList();
var ovrMethodParams = overridenMethod.Parameters.ToList();
if (exprMethodArgs.Count != ovrMethodParams.Count)
{
continue;
}
var paramMismatch = false;
for (int i = 0; i < exprMethodArgs.Count; i++)
{
var arg = exprMethodArgs[i];
var argType = context.SemanticModel.GetTypeInfo(arg.Expression);
var param = arg.NameColon != null ?
ovrMethodParams.FirstOrDefault(p => p.Name.ToString() == arg.NameColon.Name.ToString()) :
ovrMethodParams[i];
if (param == null || argType.Type != param.Type)
{
paramMismatch = true;
break;
}
exprMethodArgs.Remove(arg);
ovrMethodParams.Remove(param);
i--;
}
// If there are any parameters left without default value
// then it is not the base method overload we are looking for
if (ovrMethodParams.Any(p => p.HasExplicitDefaultValue))
{
continue;
}
if (!paramMismatch)
{
// If the actual arguments match with the method params
// then the base method invokation was found
// and there is no need to continue the search
return;
}
}
}
var diag = Diagnostic.Create(Rule, mds.GetLocation(), methodName);
context.ReportDiagnostic(diag);
}
}
Der CodeFix Anbieter:
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(BaseMethodCallCodeFixProvider)), Shared]
public class BaseMethodCallCodeFixProvider : CodeFixProvider
{
private const string title = "Add base method invocation";
public sealed override ImmutableArray<string> FixableDiagnosticIds
{
get { return ImmutableArray.Create(RequiredBaseMethodCallAnalyzer.DiagnosticId); }
}
public sealed override FixAllProvider GetFixAllProvider()
{
// See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/FixAllProvider.md for more information on Fix All Providers
return WellKnownFixAllProviders.BatchFixer;
}
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;
// Register a code action that will invoke the fix.
context.RegisterCodeFix(
CodeAction.Create(
title: title,
createChangedDocument: c => AddBaseMethodCallAsync(context.Document, diagnosticSpan, c),
equivalenceKey: title),
diagnostic);
}
private async Task<Document> AddBaseMethodCallAsync(Document document, TextSpan diagnosticSpan, CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken);
var node = root.FindNode(diagnosticSpan) as MethodDeclarationSyntax;
var args = new List<ArgumentSyntax>();
foreach (var param in node.ParameterList.Parameters)
{
args.Add(SyntaxFactory.Argument(SyntaxFactory.ParseExpression(param.Identifier.ValueText)));
}
var argsList = SyntaxFactory.SeparatedList(args);
var exprStatement = SyntaxFactory.ExpressionStatement(
SyntaxFactory.InvocationExpression(
SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
SyntaxFactory.BaseExpression(),
SyntaxFactory.Token(SyntaxKind.DotToken),
SyntaxFactory.IdentifierName(node.Identifier.ToString())
),
SyntaxFactory.ArgumentList(argsList)
),
SyntaxFactory.Token(SyntaxKind.SemicolonToken)
);
var newBodyStatements = SyntaxFactory.Block(node.Body.Statements.Insert(0, exprStatement));
var newRoot = root.ReplaceNode(node.Body, newBodyStatements).WithAdditionalAnnotations(Simplifier.Annotation);
return document.WithSyntaxRoot(newRoot);
}
}
Und eine Demo wie es funktioniert: http://screencast.com/t/4Jgm989TI
Da ich völlig neu in der .NET Compiler Platform bin, hätte ich gerne Feedback und Vorschläge, wie ich meine Lösung verbessern kann. Vielen Dank im Voraus!
Aber Sie müssen nicht die Basismethode auslösen. Also gibt es keine Möglichkeit für den Compiler, dies zu tun, da es kein Kompilierungsfehler ist. – Liam
IntelliJ ist Java. Ihre Codierung in C#.Diese Funktionalität existiert nicht in C# – Liam
Ich verstehe, dass mein Code in C# und nicht Java ist und deshalb suche ich nach einem Weg, um das gleiche Ergebnis wie in IntelliJ, aber mit VS –