2016-06-22 3 views
1

基本クラスの特定のメソッドをオーバーライドしてオーバーライドされたメソッドの基本メソッドを呼び出すことを忘れた場合、Visual Studioで警告を発生させる方法を研究しています。たとえば、基本メソッドが呼び出されていない場合のビジュアルスタジオの警告

これまでのところ、私は方法を見つけることができませんでした。おそらく、コンパイラがこのような警告を直接発するようにすることはできません。サードパーティのツールであっても、新しいRoslyn .NETコンパイラプラットフォームのAPIを使用していても、これを行うためのアイデアはありますか?

UPDATE: たとえば、AndroidStudio(IntelliJの)あなたはどんな活動でonCreate()をオーバーライドするが、基本メソッドsuper.onCreate()を呼び出すことを忘れた場合に、警告を取得します。それが私がVSで必要とする行動です。あなたはいくつかのコードを確認したい場合は

+0

しかし、基本メソッドを起動する必要はありません。コンパイルエラーではないため、コンパイラがこれを行う方法はありません。 – Liam

+0

IntelliJはJavaです。あなたのコーディングはC#で。このファンクションはC#には存在しません – Liam

+0

私は自分のコードがJavaではなくC#であることを理解しています。そのため、IntelliJと同じ結果を得る方法を探していますが、VS –

答えて

1

最後にRoslynを試してみる時間がありましたが、アナライザーで解決策を見つけたようです。これが私の解決です。

属性サブクラスでオーバーライドする必要があるメソッドをマークする:

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

アナライザ:

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

CodeFixプロバイダ:

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

とデモ仕組み:http://screencast.com/t/4Jgm989TI

私は.NETコンパイラプラットフォームが全く新しいので、私のソリューションを改善する方法についてのフィードバックや提案があります。前もって感謝します!

3

あなたのデザインに変更する必要があり、その後に実行されます。このようA()

abstract class Foo 
{ 
    protected abstract void PostA(); 

    public void A() { 
     ... 
     PostA(); 
    } 
} 


class Bar : Foo 
{ 
    protected override void PostA() 
    { 

    } 
} 

//method signature remains the same: 
Bar.A(); 

は常に

複数の継承を有し、Aを確認するために、オーバーライドされたメソッドの前に解雇されました

abstract class Bar : Foo 
{ 
    //no need to override now 
} 

class Baz:Bar 
{ 
    protected override void PostA() 
    { 

    } 
} 

正確を行う方法はありません:()と呼ばれているあなたにもバーの要約を作成する必要がありますあなたはC#で何をしたいですか?これはVisual Studioの問題ではありません。これがC#の仕組みです。

仮想メソッドシグネチャは、ベースで呼び出されるかどうかを指定することができます。バーチャルまたは抽象の2つのオプションがあります。あなたはvirtualを使用しており、私はあなたにabstractの解を与えました。どちらを使いたいかはあなた次第です。

私が考えることのできる最も近いものは、#warningです。 this answerを参照してください。ただし、IntellisenseではなくOutputウィンドウでのみ警告が表示されます。基本的にはC# does not support custom compiler warningsです。

+0

プラスです。 'A'がC#でオーバーライドされるのを防ぐことができますか? – Bathsheba

+0

問題は、基本クラスの私のPostAメソッドは、本体を持っているため抽象クラスにすることができないということです。 –

+4

ボディをA()に入れます。あなたが上書きしたいコードだけがPostAに入ります – Liam

関連する問題