File: Language\RazorDiagnostic.cs
Web Access
Project: src\src\Razor\src\Compiler\Microsoft.CodeAnalysis.Razor.Compiler\src\Microsoft.CodeAnalysis.Razor.Compiler.csproj (Microsoft.CodeAnalysis.Razor.Compiler)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using Microsoft.AspNetCore.Razor.Utilities;
using Microsoft.AspNetCore.Razor.Utilities.Shared.Resources;
 
namespace Microsoft.AspNetCore.Razor.Language;
 
public sealed class RazorDiagnostic : IEquatable<RazorDiagnostic>, IFormattable
{
    private readonly RazorDiagnosticDescriptor _descriptor;
    private readonly object[] _args;
 
    public string Id => _descriptor.Id;
    public RazorDiagnosticSeverity Severity => _descriptor.Severity;
    public int WarningLevel => _descriptor.WarningLevel;
    public SourceSpan Span { get; }
 
    private Checksum? _checksum;
 
    private RazorDiagnostic(RazorDiagnosticDescriptor descriptor, SourceSpan? span, object[]? args)
    {
        _descriptor = descriptor ?? throw new ArgumentNullException(nameof(descriptor));
        Span = span ?? SourceSpan.Undefined;
        _args = args ?? [];
    }
 
    public static RazorDiagnostic Create(RazorDiagnosticDescriptor descriptor)
        => new(descriptor, span: null, args: null);
 
    public static RazorDiagnostic Create(RazorDiagnosticDescriptor descriptor, SourceSpan? span)
        => new(descriptor, span, args: null);
 
    public static RazorDiagnostic Create(RazorDiagnosticDescriptor descriptor, params object[] args)
        => new(descriptor, span: null, args);
 
    public static RazorDiagnostic Create(RazorDiagnosticDescriptor descriptor, SourceSpan? span, params object[] args)
        => new(descriptor, span, args);
 
    internal Checksum Checksum
        => _checksum ??= ComputeChecksum();
 
    private Checksum ComputeChecksum()
    {
        var builder = new Checksum.Builder();
 
        builder.Append(_descriptor.Id);
        builder.Append((int)_descriptor.Severity);
        builder.Append(_descriptor.MessageFormat);
 
        foreach (var arg in _args)
        {
            builder.Append(ConvertEnumIfNeeded(arg));
        }
 
        var span = Span;
        builder.Append(span.FilePath);
        builder.Append(span.AbsoluteIndex);
        builder.Append(span.LineIndex);
        builder.Append(span.CharacterIndex);
        builder.Append(span.Length);
        builder.Append(span.LineCount);
        builder.Append(span.EndCharacterIndex);
 
        return builder.FreeAndGetChecksum();
 
        static object ConvertEnumIfNeeded(object value)
        {
            if (value.GetType() is { IsEnum: true } enumType)
            {
                var underlyingType = Enum.GetUnderlyingType(enumType);
 
                // Currently, only enums with an underlying type of Int32 are supported,
                // but more can be added if needed.
                if (underlyingType != typeof(int))
                {
                    throw new NotSupportedException(SR.FormatUnsupported_type_0(enumType.FullName));
                }
 
                return Convert.ToInt32(value);
            }
 
            return value;
        }
    }
 
    public string GetMessage()
        => GetMessage(formatProvider: null);
 
    public string GetMessage(IFormatProvider? formatProvider)
        => string.Format(formatProvider, _descriptor.MessageFormat, _args);
 
    public override bool Equals(object? obj)
        => obj is RazorDiagnostic diagnostic &&
           Equals(diagnostic);
 
    public bool Equals(RazorDiagnostic? other)
        => other is not null &&
           Checksum.Equals(other.Checksum);
 
    public override int GetHashCode()
        => Checksum.GetHashCode();
 
    public override string ToString()
    {
        return Format(this, formatProvider: null);
    }
 
    string IFormattable.ToString(string? format, IFormatProvider? formatProvider)
        => Format(this, formatProvider);
 
    private static string Format(RazorDiagnostic diagnostic, IFormatProvider? formatProvider)
    {
        var span = diagnostic.Span;
        var severity = diagnostic.Severity;
        var id = diagnostic.Id;
        var message = diagnostic.GetMessage(formatProvider);
 
        // Our indices are 0-based, but we we want to print them as 1-based.
        return $"{span.FilePath}({span.LineIndex + 1},{span.CharacterIndex + 1}): {severity} {id}: {message}";
    }
}