如果未调用基本方法,Visual Studio警告 [英] visual studio warning if base method is not called

查看:50
本文介绍了如果未调用基本方法,Visual Studio警告的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在研究一种方法,如果我在基类中重写特定方法但忘记在被重写的基类中调用该基方法,则可以使Visual Studio发出警告.例如:

  Foo类{[SomeAttributeToMarkTheMethodToFireTheWarning]公共虚拟无效A(){...}}酒吧:富{公共重写void A(){//base.A();//如果未调用base.A()则警告//...}} 

到目前为止,我找不到办法,可能无法使编译器直接发出这样的警告.有什么想法可以做到这一点,即使它是第三方工具还是使用新的Roslyn .NET编译器平台中的某些API?

更新:例如,在AndroidStudio(IntelliJ)中,如果您在任何活动中都覆盖了 onCreate(),但忘记调用基本方法 super.onCreate(),则会收到警告.这就是我在VS中需要的行为.

解决方案

我终于有一些时间来对Roslyn进行实验,看来我找到了使用分析仪的解决方案.这是我的解决方法.

用于标记需要在子类中覆盖的方法的属性:

  [AttributeUsage(AttributeTargets.Method,Inherited = false,AllowMultiple = false)]公共密封类RequireBaseMethodCallAttribute:Attribute{公共RequireBaseMethodCallAttribute(){}} 

分析仪:

  [DiagnosticAnalyzer(LanguageNames.CSharp)]公共类RequiredBaseMethodCallAnalyzer:DiagnosticAnalyzer{公共常量字符串DiagnosticId ="RequireBaseMethodCall";//您可以在Resources.resx文件中更改这些字符串.如果您不希望分析器可本地化,则可以对Title和MessageFormat使用常规字符串.//有关更多的本地化信息,请参见https://github.com/dotnet/roslyn/blob/master/docs/analyzers/Localizing%20Analyzers.md私有静态只读LocalizableString标题= new LocalizableResourceString(nameof(Resources.AnalyzerTitle),Resources.ResourceManager,typeof(Resources));私有静态只读LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AnalyzerMessageFormat),Resources.ResourceManager,typeof(Resources));私有静态只读LocalizableString描述= new LocalizableResourceString(nameof(Resources.AnalyzerDescription),Resources.ResourceManager,typeof(Resources));私有常量字符串Category ="Usage";私有静态DiagnosticDescriptor规则=新的DiagnosticDescriptor(DiagnosticId,标题,MessageFormat,类别,DiagnosticSeverity.Warning,isEnabledByDefault:true,描述:Description);公共重写ImmutableArray< DiagnosticDescriptor>SupportedDiagnostics {get {return ImmutableArray.Create(Rule);}}公共重写void Initialize(AnalysisContext上下文){context.RegisterCompilationStartAction(AnalyzeMethodForBaseCall);}私有静态无效AnalyzeMethodForBaseCall(CompilationStartAnalysisContext CompilationStartContext){CompilationStartContext.RegisterSyntaxNodeAction(AnalyzeMethodDeclaration,SyntaxKind.MethodDeclaration);}私有静态void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext上下文){var mds = context.Node作为MethodDeclarationSyntax;如果(mds == null){返回;}IMethodSymbol符号= context.SemanticModel.GetDeclaredSymbol(mds)作为IMethodSymbol;如果(符号==空){返回;}如果(!symbol.IsOverride){返回;}如果(symbol.OverriddenMethod == null){返回;}var overlaynMethod = symbol.OverriddenMethod;var attrs = overlaynMethod.GetAttributes();如果(!attrs.Any(ad => ad.AttributeClass.MetadataName.ToUpperInvariant()== typeof(RequireBaseMethodCallAttribute).Name.ToUpperInvariant())){返回;}var overlaynMethodName = overlaynMethod.Name.ToString();字符串methodName = overlaynMethodName;var调用= mds.DescendantNodes().OfType< MemberAccessExpressionSyntax>().ToList();foreach(调用中的var inv){var expr = inv.Expression;如果((SyntaxKind)expr.RawKind == SyntaxKind.BaseExpression){var memberAccessExpr = expr.Parent作为MemberAccessExpressionSyntax;如果(memberAccessExpr == null){继续;}//比较exprSymbol和overridenMethodvar exprMethodName = memberAccessExpr.Name.ToString();如果(exprMethodName!= overlaynMethodName){继续;}var invokationExpr = memberAccessExpr.Parent作为InvocationExpressionSyntax;如果(invokationExpr == null){继续;}var exprMethodArgs = invokationExpr.ArgumentList.Arguments.ToList();var ovrMethodParams = overlaynMethod.Parameters.ToList();如果(exprMethodArgs.Count!= ovrMethodParams.Count){继续;}var paramMismatch = false;对于(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;休息;}exprMethodArgs.Remove(arg);ovrMethodParams.Remove(param);一世 - ;}//如果还有没有默认值的参数//那么这不是我们要寻找的基本方法重载如果(ovrMethodParams.Any(p => p.HasExplicitDefaultValue)){继续;}如果(!paramMismatch){//如果实际参数与方法params匹配//然后找到基本方法调用//并且无需继续搜索返回;}}}var diag = Diagnostic.Create(Rule,mds.GetLocation(),methodName);context.ReportDiagnostic(diag);}} 

CodeFix提供程序:

  [ExportCodeFixProvider(LanguageNames.CSharp,Name = nameof(BaseMethodCallCodeFixProvider)),共享]公共类BaseMethodCallCodeFixProvider:CodeFixProvider{private const string title =添加基本方法调用";公共密封重写ImmutableArray< string>FixableDiagnosticIds{得到{返回ImmutableArray.Create(RequiredBaseMethodCallAnalyzer.DiagnosticId);}}公共密封重写FixAllProvider GetFixAllProvider(){//有关修正所有提供程序的更多信息,请参见https://github.com/dotnet/roslyn/blob/master/docs/analyzers/FixAllProvider.md返回WellKnownFixAllProviders.BatchFixer;}公共密封重写异步任务RegisterCodeFixesAsync(CodeFixContext上下文){var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);var diagnostic = context.Diagnostics.First();var diagnosticSpan = diagnostic.Location.SourceSpan;//注册将调用该修订的代码操作.context.RegisterCodeFix(CodeAction.Create(标题:标题,createChangedDocument:c =>AddBaseMethodCallAsync(context.Document,diagnosticSpan,c),equivalenceKey:标题),诊断);}私有异步Task< Document>AddBaseMethodCallAsync(文档文档,TextSpan诊断范围,CancellationToken取消令牌){var root =等待文档.GetSyntaxRootAsync(cancellationToken);var node = root.FindNode(diagnosticSpan)as MethodDeclarationSyntax;var args = new List< ArgumentSyntax>();foreach(node.ParameterList.Parameters中的var参数){args.Add(SyntaxFactory.Argument(SyntaxFactory.ParseExpression(param.Identifier.ValueText))));}var argsList = SyntaxFactory.SeparatedList(args);var exprStatement = SyntaxFactory.ExpressionStatement(语法Factory.InvocationExpression(语法Factory.MemberAccessExpression(语法Kind.SimpleMemberAccessExpression,语法工厂.BaseExpression(),SyntaxFactory.Token(SyntaxKind.DotToken),语法Factory.IdentifierName(node.Identifier.ToString())),语法Factory.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);返回document.WithSyntaxRoot(newRoot);}} 

以及一个演示其工作原理的示例: http://screencast.com/t/4Jgm989TI

由于我对.NET编译器平台完全陌生,所以我很乐意就如何改进解决方案提供任何反馈和建议.预先谢谢你!

I am researching a way to make Visual Studio fire a warning if I override a specific method in a base class but forget to call the base method in the overridden one. E.g:

class Foo
{
   [SomeAttributeToMarkTheMethodToFireTheWarning]
   public virtual void A() { ... }
}

class Bar : Foo
{
   public override void A()
   {
      // base.A(); // warning if base.A() is not called
      // ...
   }
}

So far I couldn't find a way and probably it is not possible to make the compiler fire such a warning directly. Any ideas for a way to do it, even if it's a 3rd-party tool or using some API from the new Roslyn .NET compiler platform?

UPDATE: For example, in AndroidStudio (IntelliJ) if you override onCreate() in any activity but forget to call the base method super.onCreate(), you get a warning. That's the behavior I need in VS.

解决方案

I finally had some time to experiment with Roslyn and looks like I found a solution with an analyzer. This is my solution.

The attribute to mark the method that needs to be overriden in the subclass:

[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
public sealed class RequireBaseMethodCallAttribute : Attribute
{
    public RequireBaseMethodCallAttribute() { }
}

The analyzer:

[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);
    }
}

The CodeFix provider:

[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);
    }
}

And a demo how it works: http://screencast.com/t/4Jgm989TI

Since I am totally new to the .NET Compiler Platform, I would love to have any feedback and suggestions on how to improve my solution. Thank you in advance!

这篇关于如果未调用基本方法,Visual Studio警告的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆