File: Diagnostics\DiagnosticDataTests.cs
Web Access
Project: src\src\EditorFeatures\Test\Microsoft.CodeAnalysis.EditorFeatures.UnitTests.csproj (Microsoft.CodeAnalysis.EditorFeatures.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics;
 
[UseExportProvider]
[Trait(Traits.Feature, Traits.Features.Diagnostics)]
public class DiagnosticDataTests
{
    [Fact]
    public async Task DiagnosticData_GetText()
    {
        var code = "";
        await VerifyTextSpanAsync(code, 10, 10, 20, 20, new TextSpan(0, 0));
    }
 
    [Fact]
    public async Task DiagnosticData_GetText1()
    {
        var code = @"
";
 
        await VerifyTextSpanAsync(code, 30, 30, 40, 40, new TextSpan(code.Length, 0));
    }
 
    [Fact]
    public async Task DiagnosticData_GetText2()
    {
        var code = @"
";
 
        await VerifyTextSpanAsync(code, 0, 30, 40, 40, new TextSpan(code.Length, 0));
    }
 
    [Fact]
    public async Task DiagnosticData_GetText3()
    {
        var code = @"
";
 
        await VerifyTextSpanAsync(code, 0, 30, 0, 40, new TextSpan(code.Length, 0));
    }
 
    [Fact]
    public async Task DiagnosticData_GetText4()
    {
        var code = @"
";
 
        await VerifyTextSpanAsync(code, 1, 30, 1, 40, new TextSpan(code.Length, 0));
    }
 
    [Fact]
    public async Task DiagnosticData_GetText5()
    {
        var code = @"
";
 
        await VerifyTextSpanAsync(code, 1, 30, 1, 40, new TextSpan(code.Length, 0));
    }
 
    [Fact]
    public async Task DiagnosticData_GetText6()
    {
        var code = @"
";
 
        await VerifyTextSpanAsync(code, 1, 30, 2, 40, new TextSpan(code.Length, 0));
    }
 
    [Fact]
    public async Task DiagnosticData_GetText7()
    {
        var code = @"
";
 
        await VerifyTextSpanAsync(code, 1, 0, 1, 2, new TextSpan(code.Length, 0));
    }
 
    [Fact]
    public async Task DiagnosticData_GetText8()
    {
        var code = @"
namespace B
{
    class A
    {
    }
}
";
 
        await VerifyTextSpanAsync(code, 3, 10, 3, 11, new TextSpan(28, 1));
    }
 
    private static async Task VerifyTextSpanAsync(string code, int startLine, int startColumn, int endLine, int endColumn, TextSpan span)
    {
        using var workspace = new TestWorkspace(composition: EditorTestCompositions.EditorFeatures);
        var document = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp).AddDocument("TestDocument", code);
 
        var data = new DiagnosticData(
            id: "test1",
            category: "Test",
            message: "test1 message",
            severity: DiagnosticSeverity.Info,
            defaultSeverity: DiagnosticSeverity.Info,
            isEnabledByDefault: false,
            warningLevel: 1,
            projectId: document.Project.Id,
            customTags: ImmutableArray<string>.Empty,
            properties: ImmutableDictionary<string, string>.Empty,
            location: new DiagnosticDataLocation(new("originalFile1", new(startLine, startColumn), new(endLine, endColumn)), document.Id),
            language: document.Project.Language);
 
        var text = await document.GetTextAsync();
        var actual = data.DataLocation.UnmappedFileSpan.GetClampedTextSpan(text);
 
        Assert.Equal(span, actual);
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/46377")]
    public async Task DiagnosticData_ExternalAdditionalLocationIsPreserved()
    {
        using var workspace = new TestWorkspace(composition: EditorTestCompositions.EditorFeatures);
 
        var additionalDocument = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp)
            .AddDocument("test.cs", "", filePath: "test.cs")
            .Project.AddAdditionalDocument("AdditionalDocument.txt", "First line in file", filePath: "AdditionalDocument.txt");
        var document = additionalDocument.Project.Documents.Single();
 
        var externalAdditionalLocation = new DiagnosticDataLocation(
            new(additionalDocument.Name, new(0, 0), new(0, 1)), additionalDocument.Id);
 
        var diagnosticData = new DiagnosticData(
            id: "test1",
            category: "Test",
            message: "test1 message",
            severity: DiagnosticSeverity.Info,
            defaultSeverity: DiagnosticSeverity.Info,
            isEnabledByDefault: true,
            warningLevel: 1,
            projectId: document.Project.Id,
            customTags: ImmutableArray<string>.Empty,
            properties: ImmutableDictionary<string, string>.Empty,
            location: new DiagnosticDataLocation(new FileLinePositionSpan(document.FilePath, span: default), document.Id),
            additionalLocations: ImmutableArray.Create(externalAdditionalLocation),
            language: document.Project.Language);
 
        var diagnostic = await diagnosticData.ToDiagnosticAsync(document.Project, CancellationToken.None);
        var roundTripDiagnosticData = DiagnosticData.Create(diagnostic, document);
 
        var roundTripAdditionalLocation = Assert.Single(roundTripDiagnosticData.AdditionalLocations);
        Assert.Equal(externalAdditionalLocation.DocumentId, roundTripAdditionalLocation.DocumentId);
        Assert.Equal(externalAdditionalLocation.UnmappedFileSpan, roundTripAdditionalLocation.UnmappedFileSpan);
    }
 
    [Fact]
    public async Task DiagnosticData_SourceGeneratedDocumentLocationIsPreserved()
    {
        var content = @"
namespace B
{
    class A
    {
    }
}
";
        using var workspace = TestWorkspace.CreateCSharp(files: [], sourceGeneratedFiles: [content], composition: EditorTestCompositions.EditorFeatures);
        var hostDocument = workspace.Documents.Single();
        Assert.True(hostDocument.IsSourceGenerated);
 
        var documentId = hostDocument.Id;
        var project = workspace.CurrentSolution.GetRequiredProject(documentId.ProjectId);
        var document = await project.GetSourceGeneratedDocumentAsync(documentId, CancellationToken.None);
 
        await VerifyTextSpanAsync(content, 3, 10, 3, 11, new TextSpan(28, 1));
        var location = new DiagnosticDataLocation(
            new(document.FilePath, new(3, 10), new(3, 11)), documentId);
 
        var diagnosticData = new DiagnosticData(
            id: "test1",
            category: "Test",
            message: "test1 message",
            severity: DiagnosticSeverity.Info,
            defaultSeverity: DiagnosticSeverity.Info,
            isEnabledByDefault: true,
            warningLevel: 1,
            projectId: documentId.ProjectId,
            customTags: ImmutableArray<string>.Empty,
            properties: ImmutableDictionary<string, string>.Empty,
            location: location,
            additionalLocations: ImmutableArray<DiagnosticDataLocation>.Empty,
            language: project.Language);
 
        var diagnostic = await diagnosticData.ToDiagnosticAsync(project, CancellationToken.None);
        var roundTripDiagnosticData = DiagnosticData.Create(diagnostic, document);
 
        var roundTripLocation = roundTripDiagnosticData.DataLocation;
        Assert.NotNull(roundTripDiagnosticData.DataLocation);
        Assert.Equal(location.DocumentId, roundTripLocation.DocumentId);
        Assert.Equal(location.UnmappedFileSpan, roundTripLocation.UnmappedFileSpan);
    }
 
    [Theory, CombinatorialData]
    [WorkItem("https://devdiv.visualstudio.com/DefaultCollection/DevDiv/_workitems/edit/1676229")]
    public async Task DiagnosticData_SourceFileAdditionalLocationIsPreserved(bool testDifferentProject, bool testRemovedDocument)
    {
        using var workspace = new TestWorkspace(composition: EditorTestCompositions.EditorFeatures);
 
        var firstDocument = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp)
            .AddDocument("test.cs", "class C1 { }", filePath: "test.cs");
        Document secondDocument;
        if (testDifferentProject)
        {
            secondDocument = firstDocument.Project.Solution.AddProject("TestProject2", "TestProject2", LanguageNames.CSharp)
                .AddDocument("test2.cs", "class C2 { }", filePath: "test2.cs");
        }
        else
        {
            secondDocument = firstDocument.Project.AddDocument("test2.cs", "class C2 { }", filePath: "test2.cs");
        }
 
        firstDocument = secondDocument.Project.Solution.GetRequiredDocument(firstDocument.Id);
 
        var additionalLocation = new DiagnosticDataLocation(
            new(secondDocument.Name, new(0, 0), new(0, 1)), secondDocument.Id);
 
        var diagnosticData = new DiagnosticData(
            id: "test1",
            category: "Test",
            message: "test1 message",
            severity: DiagnosticSeverity.Info,
            defaultSeverity: DiagnosticSeverity.Info,
            isEnabledByDefault: true,
            warningLevel: 1,
            projectId: firstDocument.Project.Id,
            customTags: ImmutableArray<string>.Empty,
            properties: ImmutableDictionary<string, string>.Empty,
            location: new DiagnosticDataLocation(new FileLinePositionSpan(firstDocument.FilePath, span: default), firstDocument.Id),
            additionalLocations: ImmutableArray.Create(additionalLocation),
            language: firstDocument.Project.Language);
 
        var diagnostic = await diagnosticData.ToDiagnosticAsync(firstDocument.Project, CancellationToken.None);
 
        if (testRemovedDocument)
        {
            firstDocument = firstDocument.Project.Solution
                .RemoveDocument(secondDocument.Id)
                .GetRequiredDocument(firstDocument.Id);
        }
 
        var roundTripDiagnosticData = DiagnosticData.Create(diagnostic, firstDocument);
 
        var roundTripAdditionalLocation = Assert.Single(roundTripDiagnosticData.AdditionalLocations);
        var expectedAdditionalDocumentId = !testRemovedDocument ? additionalLocation.DocumentId : null;
        Assert.Equal(expectedAdditionalDocumentId, roundTripAdditionalLocation.DocumentId);
        Assert.Equal(additionalLocation.UnmappedFileSpan, roundTripAdditionalLocation.UnmappedFileSpan);
    }
}