Skip to main content
The DiagnosticDescriptor class provides a description and metadata about a specific type of diagnostic. It defines the template for creating diagnostics with a particular ID, such as what the diagnostic means, its default severity, and its message format.

Overview

While a Diagnostic represents a specific occurrence of an error or warning in code, a DiagnosticDescriptor describes the type of diagnostic. For example, there is one descriptor for “LUA1001: Identifier expected”, but there could be many individual diagnostics with that ID occurring at different locations in the code.

Key Properties

Id

The unique identifier for this type of diagnostic.
public string Id { get; }
Example: "LUA1001", "LUA0004", "CUSTOM001"

Title

A short, localizable title describing the diagnostic.
public LocalizableString Title { get; }
Example: "Identifier expected", "Invalid number format"

MessageFormat

A format string used to generate the diagnostic message, which can include placeholders for arguments.
public LocalizableString MessageFormat { get; }
Example: "Expected identifier but found '{0}'", "Numeric literal '{0}' is too large" When a diagnostic is created, arguments are passed to fill in these placeholders using string.Format.

Category

The category of the diagnostic, used for grouping related diagnostics.
public string Category { get; }
Common values:
  • "Compiler" - For Loretta’s built-in compiler diagnostics
  • Custom categories for analyzer diagnostics

DefaultSeverity

The default severity level for diagnostics of this type.
public DiagnosticSeverity DefaultSeverity { get; }
Possible values:
  • DiagnosticSeverity.Error
  • DiagnosticSeverity.Warning
  • DiagnosticSeverity.Info
  • DiagnosticSeverity.Hidden

IsEnabledByDefault

Whether diagnostics of this type are enabled by default.
public bool IsEnabledByDefault { get; }

Description

An optional longer description providing more context about the diagnostic.
public LocalizableString Description { get; }

HelpLinkUri

An optional URL providing more detailed information about the diagnostic.
public string HelpLinkUri { get; }

CustomTags

Optional custom tags for the diagnostic, used for specialized handling.
public IEnumerable<string> CustomTags { get; }

How Descriptors Relate to Diagnostics

Every Diagnostic has an associated DiagnosticDescriptor accessible through the Descriptor property:
var tree = LuaSyntaxTree.ParseText("if then end");
var diagnostics = tree.GetDiagnostics();

foreach (var diagnostic in diagnostics)
{
    var descriptor = diagnostic.Descriptor;
    
    Console.WriteLine($"ID: {descriptor.Id}");
    Console.WriteLine($"Title: {descriptor.Title}");
    Console.WriteLine($"Category: {descriptor.Category}");
    Console.WriteLine($"Default Severity: {descriptor.DefaultSeverity}");
    Console.WriteLine($"Message: {diagnostic.GetMessage()}");
    Console.WriteLine();
}

Where Loretta’s Descriptors Are Defined

Loretta’s built-in diagnostic descriptors are defined internally and created from the ErrorCode enum. The descriptors are generated using:
  • ErrorFacts.GetMessageFormat() - Gets the message format for an error code
  • ErrorFacts.GetTitle() - Gets the title for an error code
  • ErrorFacts.GetDescription() - Gets the description for an error code
  • ErrorFacts.GetCategory() - Gets the category for an error code
  • ErrorFacts.GetSeverity() - Gets the default severity for an error code
These are managed by the MessageProvider class, which uses the code prefix "LUA" for all Loretta diagnostics.

Creating Custom Descriptors

You can create your own diagnostic descriptors for custom analyzers or tools:
using Loretta.CodeAnalysis;

// Create a descriptor for a custom warning
var descriptor = new DiagnosticDescriptor(
    id: "CUSTOM001",
    title: "Unused variable",
    messageFormat: "Variable '{0}' is declared but never used",
    category: "Usage",
    defaultSeverity: DiagnosticSeverity.Warning,
    isEnabledByDefault: true,
    description: "Variables should be used after declaration to avoid confusion.",
    helpLinkUri: "https://example.com/docs/CUSTOM001"
);

With Localization

For localizable strings, you can use LocalizableResourceString:
var descriptor = new DiagnosticDescriptor(
    id: "CUSTOM002",
    title: new LocalizableResourceString(
        nameOfLocalizableResource: nameof(Resources.CUSTOM002_Title),
        resourceManager: Resources.ResourceManager,
        resourceSource: typeof(Resources)
    ),
    messageFormat: new LocalizableResourceString(
        nameOfLocalizableResource: nameof(Resources.CUSTOM002_MessageFormat),
        resourceManager: Resources.ResourceManager,
        resourceSource: typeof(Resources)
    ),
    category: "Usage",
    defaultSeverity: DiagnosticSeverity.Info,
    isEnabledByDefault: true
);

Accessing Descriptor from Diagnostic

A common pattern is to access the descriptor to get metadata about a diagnostic:
public void ProcessDiagnostic(Diagnostic diagnostic)
{
    var descriptor = diagnostic.Descriptor;
    
    // Check if this is a specific type of diagnostic
    if (descriptor.Id == "LUA1001")
    {
        Console.WriteLine("Found identifier expected error");
    }
    
    // Check category
    if (descriptor.Category == "Compiler")
    {
        Console.WriteLine("Compiler diagnostic");
    }
    
    // Check if enabled by default
    if (!descriptor.IsEnabledByDefault)
    {
        Console.WriteLine("This diagnostic is not enabled by default");
    }
}

Filtering Diagnostics by Descriptor

You can use descriptors to filter or categorize diagnostics:
var tree = LuaSyntaxTree.ParseText(luaCode);
var diagnostics = tree.GetDiagnostics();

// Filter by ID
var syntaxErrors = diagnostics
    .Where(d => d.Descriptor.Id.StartsWith("LUA1"));

// Filter by category
var compilerDiagnostics = diagnostics
    .Where(d => d.Descriptor.Category == "Compiler");

// Group by severity
var grouped = diagnostics
    .GroupBy(d => d.Descriptor.DefaultSeverity);

foreach (var group in grouped)
{
    Console.WriteLine($"{group.Key}:");
    foreach (var diagnostic in group)
    {
        Console.WriteLine($"  {diagnostic.Id}: {diagnostic.GetMessage()}");
    }
}

Use Case: Custom Diagnostic Suppression

Descriptors are useful for implementing custom diagnostic filtering or suppression:
public class DiagnosticFilter
{
    private readonly HashSet<string> _suppressedIds = new();
    
    public void SuppressDiagnostic(string id)
    {
        _suppressedIds.Add(id);
    }
    
    public IEnumerable<Diagnostic> FilterDiagnostics(
        IEnumerable<Diagnostic> diagnostics)
    {
        return diagnostics.Where(d => 
            !_suppressedIds.Contains(d.Descriptor.Id));
    }
}

// Usage
var filter = new DiagnosticFilter();
filter.SuppressDiagnostic("LUA0022"); // Suppress line break warnings

var tree = LuaSyntaxTree.ParseText(luaCode);
var allDiagnostics = tree.GetDiagnostics();
var filteredDiagnostics = filter.FilterDiagnostics(allDiagnostics);

Equality

Diagnostic descriptors implement value equality based on all their properties:
var descriptor1 = new DiagnosticDescriptor(
    "CUSTOM001", "Title", "Message", "Category",
    DiagnosticSeverity.Warning, true);
    
var descriptor2 = new DiagnosticDescriptor(
    "CUSTOM001", "Title", "Message", "Category",
    DiagnosticSeverity.Warning, true);

// These are equal
bool areEqual = descriptor1.Equals(descriptor2); // true

See Also

Build docs developers (and LLMs) love