Search code examples
c#visual-studioroslynstatic-code-analysisroslyn-code-analysis

SyntaxNode.ContainsDiagnostics not working with your own diagnostics?


I am trying to write an analyzer that prevents users from providing parameters that are automatically provided (ex: by the compiler with [CallerMemberName]) and I want the analyzer to error you when you have provided a parameter that shouldn't be provided (to tell a parameter shouldn't be provided I created an attribute : DontProvideAttribute).

The thing is such automatically provided parameters must be optional (otherwise the value provided by the user would be written over the one provided automatically) so I've made a second analyze to prevent users from using [DontProvide] on non-optional parameters.

And there comes the problem, I want the error on the method invocation to be there only if the parameter declaration doesn't have the [DontProvide] should only be used on optional parameters error which

foreach (SyntaxReference parameterDefinition in parameter.DeclaringSyntaxReferences)
{
    if (parameterDefinition.GetSyntax().ContainsDiagnostics)
    {
        return;
    }
}

should complete this but it seems like it doesn't consider diagnostics you reported yourself.

What i have tried :

-Changing the order of diagnostics to make the declaration being analyzed before the method invocation

-Use .GetDiagnostics().Count() > 0 instead

-Changing the order of the text in the analyzed document to have the method declaration above the method invocation

The analyzer :

public override void Initialize(AnalysisContext context)
{
    context.RegisterSymbolAction(AnalyzeParametersDeclaration, SymbolKind.Parameter);
    context.RegisterOperationAction(AnalyzeArguments, OperationKind.Argument);
}

private void AnalyzeArguments(OperationAnalysisContext context)
{
    IArgumentOperation reference = (IArgumentOperation)context.Operation;
    IParameterSymbol parameter = reference.Parameter;
    foreach (SyntaxReference parameterDefinition in parameter.DeclaringSyntaxReferences)
    {
        if (parameterDefinition.GetSyntax().ContainsDiagnostics)
            return;
    }
    foreach (AttributeData attribute in parameter.GetAttributes())
    {
        if (attribute.AttributeClass.Name == "DontProvideAttribute")
        {
            context.ReportDiagnostic(Diagnostic.Create(DontProvide, reference.Syntax.GetLocation(), parameter.Name));
        }
    }
}

private void AnalyzeParametersDeclaration(SymbolAnalysisContext context)
{
    IParameterSymbol parameter = (IParameterSymbol)context.Symbol;
    if (parameter.GetAttributes().Any(a => a.AttributeClass.Name == "DontProvideAttribute") && !parameter.IsOptional)
    {
        context.ReportDiagnostic(Diagnostic.Create(DontProvideOnlyForOptional, parameter.Locations[0]))         
    }
}

Some test code for analyze :

using System;

namespace test
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            MyClass.MyMethod(null);
        }
    }

    internal class MyClass
    {
        public static void MyMethod([DontProvide] object parameter)
        {

        }
    }

    [AttributeUsage(AttributeTargets.Parameter)]
    public class DontProvideAttribute : Attribute
    {

    } 
}

PS : The compiler may tell you that context.RegisterSymbolAction() used with SymbolKind.Parameter isn't supported, which is wrong (see more here)


Solution

  • From the discussion here and @Kris Vandermotten's comment

    ContainsDiagnostics is only for syntactic diagnostics (i.e. diagnostics acutally inside the syntax tree) not for diagnostics reported by later passes (i.e. semantic diagnostics or your own analyzer diagnostics). Here's why : a specific syntax tree may be contained in many different semantic contexts due to roslyn being able to fork and speculate about things so in one context, the syntax may be semantically correct, and in another, it won't be as such, the diagnostics are not stored on the tree itself.

    In fact the solution in my case was pretty simple : i just had to remove

    foreach (SyntaxReference parameterDefinition in parameter.DeclaringSyntaxReferences)
    {
        if (parameterDefinition.GetSyntax().ContainsDiagnostics)
            return;
    }
    

    and add && parameter.IsOptionnal on the if statement there :

    foreach (AttributeData attribute in parameter.GetAttributes())
    {
        if (attribute.AttributeClass.Name == "DontProvideAttribute")
        {
            context.ReportDiagnostic(Diagnostic.Create(DontProvide, reference.Syntax.GetLocation(), parameter.Name));
        }
    }