File: Diagnostics\RazorDiagnosticHelper.cs
Web Access
Project: src\src\Razor\src\Razor\src\Microsoft.CodeAnalysis.Razor.Workspaces\Microsoft.CodeAnalysis.Razor.Workspaces.csproj (Microsoft.CodeAnalysis.Razor.Workspaces)
// 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 System.Collections.Immutable;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Text;
 
namespace Microsoft.CodeAnalysis.Razor.Diagnostics;
 
internal static class RazorDiagnosticHelper
{
    public static async Task<LspDiagnostic[]?> GetRazorDiagnosticsAsync(IDocumentSnapshot documentSnapshot, CancellationToken cancellationToken)
    {
        var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(cancellationToken).ConfigureAwait(false);
        var sourceText = codeDocument.Source.Text;
        var csharpDocument = codeDocument.GetRequiredCSharpDocument();
        var diagnostics = csharpDocument.Diagnostics;
 
        if (diagnostics.Length == 0)
        {
            return null;
        }
 
        return Convert(diagnostics, sourceText, documentSnapshot);
    }
 
    public static VSDiagnosticProjectInformation[] GetProjectInformation(IDocumentSnapshot? documentSnapshot)
    {
        if (documentSnapshot is null)
        {
            return [];
        }
 
        return [new VSDiagnosticProjectInformation()
                {
                    Context = null,
                    ProjectIdentifier = documentSnapshot.Project.IntermediateOutputPath,
                    ProjectName = documentSnapshot.Project.DisplayName
                }];
    }
 
    internal static LspDiagnostic[] Convert(ImmutableArray<RazorDiagnostic> diagnostics, SourceText sourceText, IDocumentSnapshot documentSnapshot)
    {
        var convertedDiagnostics = new LspDiagnostic[diagnostics.Length];
 
        var i = 0;
        foreach (var diagnostic in diagnostics)
        {
            convertedDiagnostics[i++] = ConvertToVSDiagnostic(diagnostic, sourceText, documentSnapshot);
        }
 
        return convertedDiagnostics;
    }
 
    // Internal for testing
    internal static LspDiagnosticSeverity ConvertSeverity(RazorDiagnosticSeverity severity)
    {
        return severity switch
        {
            RazorDiagnosticSeverity.Error => LspDiagnosticSeverity.Error,
            RazorDiagnosticSeverity.Warning => LspDiagnosticSeverity.Warning,
            _ => LspDiagnosticSeverity.Information,
        };
    }
 
    // Internal for testing
    internal static LspRange? ConvertSpanToRange(SourceSpan sourceSpan, SourceText sourceText)
    {
        if (sourceSpan == SourceSpan.Undefined)
        {
            return null;
        }
 
        var spanStartIndex = Math.Min(sourceSpan.AbsoluteIndex, sourceText.Length);
        var spanEndIndex = Math.Min(sourceSpan.AbsoluteIndex + sourceSpan.Length, sourceText.Length);
 
        return sourceText.GetRange(spanStartIndex, spanEndIndex);
    }
 
    // Internal for testing
    internal static VSDiagnostic ConvertToVSDiagnostic(RazorDiagnostic razorDiagnostic, SourceText sourceText, IDocumentSnapshot? documentSnapshot)
    {
        var diagnostic = new VSDiagnostic()
        {
            Message = razorDiagnostic.GetMessage(CultureInfo.InvariantCulture),
            Code = razorDiagnostic.Id,
            Source = LanguageServerConstants.RazorDiagnosticSource,
            Severity = ConvertSeverity(razorDiagnostic.Severity),
            // This is annotated as not null, but we have tests that validate the behaviour when
            // we pass in null here
            Range = ConvertSpanToRange(razorDiagnostic.Span, sourceText)!,
            Projects = GetProjectInformation(documentSnapshot)
        };
 
        return diagnostic;
    }
}