Project "Caravela" 0.3 / / Caravela Documentation / Conceptual Documentation / Creating Aspects / Reporting and Suppressing Diagnostics

Reporting and suppressing diagnostics

This article explains how to report a diagnostic (error, warning or information message) from an aspect, or to suppress a diagnostic reported by the C# compiler or another aspect.

Reporting a diagnostic

To report a diagnostic:

  1. Import the Caravela.Framework.Diagnostics namespace.

  2. Define a static field of type DiagnosticDefinition in your aspect class. DiagnosticDefinition specifies the diagnostic id, the severity, and the message formatting string.

    • For a message without formatting parameters or with weakly-typed formatting parameters, use the non-generic DiagnosticDefinition class.
    • For a message with a single strongly-typed formatting parameter, use the generic DiagnosticDefinition<T> class, e.g. DiagnosticDefinition<int>.
    • For a message with several strongly-typed formatting parameters, use the generic DiagnosticDefinition<T> with a tuple, e.g. DiagnosticDefinition<(int,string)> for a message with two formatting parameters expecting a value of type int and string.

      Warning

      The aspect framework relies on the fact that diagnostics are defined as static fields of aspect classes. You will not be able to report a diagnostic that has not been declared on an aspect class of the current project.

  3. To report a diagnostic, use the Report method of the IDiagnosticSink interface.

    • from your implementation of the BuildAspect method, use builder.Diagnostics.Report(...).
    • from a template, use meta.Diagnostics.Report(...).

      The first parameter of the Report method is optional: it specifies the declaration to which the diagnostic relates. The aspect framework computes the file, line and column of the diagnostic based on this declaration. If you don't give a value for this parameter, the diagnostic will be reported for the target declaration of the aspect.

Suppressing a diagnostic

Sometimes the C# compiler or other analyzers may report warnings to the target code of your aspects. Since neither the C# compiler nor the analyzers know about your aspect, some of these warnings may be irrelevant. As an aspect author, it is a good practice to prevent the report of irrelevant warnings.

To suppress a diagnostic:

  1. Import the Caravela.Framework.Diagnostics namespace.

  2. Define a static field of type SuppressionDefinition in your aspect class. SuppressionDefinition specifies the identifier of the diagnostic to suppress.

  3. Call the Suppress method using builder.Diagnostics.Suppress(...) in the BuildAspect method or meta.Diagnostics.Suppress(...) in a template method.

Example

The following aspect can be added to a field or property. It overrides the getter so that its value is retrieved from a service locator. This aspect assumes that the target class has a field named _serviceProvider and of type IServiceProvider. The aspect reports errors if this field is absent or of a wrong type. The C# compiler may report an error CS0169 because it looks from source code that the _serviceProvider field is unused. Therefore, the aspect must suppress this diagnostic.

                using System;
using System.Linq;
using Caravela.Framework.Aspects;
using Caravela.Framework.Code;
using Caravela.Framework.Diagnostics;

namespace Caravela.Documentation.SampleCode.AspectFramework.ImportService
{
    internal class ImportAspect : OverrideFieldOrPropertyAspect
    {
        private static readonly DiagnosticDefinition<INamedType> _serviceProviderFieldMissing = new(
             "MY001",
             Severity.Error,
             "The 'ImportServiceAspect' aspects requires the type '{0}' to have a field named '_serviceProvider' and " +
            " of type 'IServiceProvider'.");
        private static readonly DiagnosticDefinition<(IField, IType)> _serviceProviderFieldTypeMismatch = new(
            "MY002",
            Severity.Error,
            "The type of field '{0}' must be 'IServiceProvider', but it is '{1}.");
        private static readonly SuppressionDefinition _suppressFieldIsNeverUsed = new("CS0169");

        public override void BuildAspect(IAspectBuilder<IFieldOrProperty> builder)
        {
            // Get the field _serviceProvider and check its type.
            var serviceProviderField = builder.Target.DeclaringType.Fields.OfName("_serviceProvider").SingleOrDefault();

            if (serviceProviderField == null)
            {
                builder.Diagnostics.Report(_serviceProviderFieldMissing, builder.Target.DeclaringType);
                return;
            }
            else if (!serviceProviderField.Type.Is(typeof(IServiceProvider)))
            {
                builder.Diagnostics.Report(_serviceProviderFieldTypeMismatch, (serviceProviderField, serviceProviderField.Type));
                return;
            }

            // Provide the advice.
            base.BuildAspect(builder);

            // Suppress the diagnostic.
            builder.Diagnostics.Suppress(serviceProviderField, _suppressFieldIsNeverUsed);
        }

        public override dynamic OverrideProperty
        {
            get => meta.This._serviceProvider.GetService(meta.Target.FieldOrProperty.Type.ToType());

            set => throw new NotSupportedException();
        }
    }
}

              
                using System;

namespace Caravela.Documentation.SampleCode.AspectFramework.ImportService
{
    internal class TargetCode
    {
       // readonly IServiceProvider _serviceProvider;

        [ImportAspect]
        private IFormatProvider FormatProvider { get; }

        public string Format(object? o)
        {
            return ((ICustomFormatter)this.FormatProvider.GetFormat(typeof(ICustomFormatter)))
                .Format(null, o, this.FormatProvider);
        }
    }
}

              
                // Error MY001 on `FormatProvider`: `The 'ImportServiceAspect' aspects requires the type 'TargetCode' to have a field named '_serviceProvider' and  of type 'IServiceProvider'.`