Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
<Tooling_MicrosoftCodeAnalysisTestingVersion>1.1.2-beta1.22512.1</Tooling_MicrosoftCodeAnalysisTestingVersion>
<MicrosoftVisualStudioShellPackagesVersion>17.5.0-preview-2-33117-317</MicrosoftVisualStudioShellPackagesVersion>
<MicrosoftVisualStudioPackagesVersion>17.5.274-preview</MicrosoftVisualStudioPackagesVersion>
<RoslynPackageVersion>4.6.0-1.23101.17</RoslynPackageVersion>
<RoslynPackageVersion>4.6.0-2.23112.5</RoslynPackageVersion>
<VisualStudioLanguageServerProtocolVersion>17.6.4-preview</VisualStudioLanguageServerProtocolVersion>
<MicrosoftNetCompilersToolsetVersion>4.4.0</MicrosoftNetCompilersToolsetVersion>
<MicrosoftCommonLanguageServerProtocolFrameworkPackageVersion>$(RoslynPackageVersion)</MicrosoftCommonLanguageServerProtocolFrameworkPackageVersion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.LanguageServer.Test.Common.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Testing;
Expand Down Expand Up @@ -98,6 +100,8 @@ private static AdhocWorkspace CreateCSharpTestWorkspace(

workspace.AddSolution(solutionInfo);

AddAnalyzersToWorkspace(workspace, exportProvider);

// Add document to workspace. We use an IVT method to create the DocumentInfo variable because there's
// a special constructor in Roslyn that will help identify the document as belonging to Razor.
var languageServerFactory = exportProvider.GetExportedValue<IRazorLanguageServerFactoryWrapper>();
Expand All @@ -121,6 +125,30 @@ private static AdhocWorkspace CreateCSharpTestWorkspace(
return workspace;
}

private static void AddAnalyzersToWorkspace(Workspace workspace, ExportProvider exportProvider)
{
var analyzerLoader = RazorTestAnalyzerLoader.CreateAnalyzerAssemblyLoader();

var analyzerPaths = new DirectoryInfo(AppContext.BaseDirectory).GetFiles("*.dll")
.Where(f => f.Name.StartsWith("Microsoft.CodeAnalysis.", StringComparison.Ordinal) && !f.Name.Contains("LanguageServer"))
.Select(f => f.FullName)
.ToImmutableArray();
var references = new List<AnalyzerFileReference>();
foreach (var analyzerPath in analyzerPaths)
{
if (File.Exists(analyzerPath))
{
references.Add(new AnalyzerFileReference(analyzerPath, analyzerLoader));
}
}

workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(references));

// Make sure Roslyn is producing diagnostics for our workspace
var razorTestAnalyzerLoader = exportProvider.GetExportedValue<RazorTestAnalyzerLoader>();
razorTestAnalyzerLoader.InitializeDiagnosticsServices(workspace);
}

private record CSharpFile(Uri DocumentUri, SourceText CSharpSourceText);

private class EmptyMappingService : IRazorSpanMappingService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public TestRazorDocumentServiceProvider(IRazorSpanMappingService razorSpanMappin

public bool CanApplyChange => throw new NotImplementedException();

public bool SupportDiagnostics => throw new NotImplementedException();
public bool SupportDiagnostics => true;

TService IRazorDocumentServiceProvider.GetService<TService>()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.LanguageServer.EndpointContracts;
using Microsoft.AspNetCore.Razor.LanguageServer.Extensions;
using Microsoft.CodeAnalysis.Razor.Workspaces.Extensions;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Xunit;
using Xunit.Abstractions;

namespace Microsoft.AspNetCore.Razor.LanguageServer.Diagnostics;

public class CSharpDiagnosticsEndToEndTest : SingleServerDelegatingEndpointTestBase
{
public CSharpDiagnosticsEndToEndTest(ITestOutputHelper testOutput)
: base(testOutput)
{
}

[Fact]
public async Task Handle()
{
var input = """

<div></div>

@functions
{
public void M()
{
{|CS0103:CallOnMe|}();
}
}

""";

await ValidateDiagnosticsAsync(input);
}

private async Task ValidateDiagnosticsAsync(string input)
{
TestFileMarkupParser.GetSpans(input, out input, out ImmutableDictionary<string, ImmutableArray<TextSpan>> spans);

var codeDocument = CreateCodeDocument(input);
var sourceText = codeDocument.GetSourceText();
var razorFilePath = "file://C:/path/test.razor";
var uri = new Uri(razorFilePath);
await CreateLanguageServerAsync(codeDocument, razorFilePath);
var documentContext = CreateDocumentContext(uri, codeDocument);
var requestContext = new RazorRequestContext(documentContext, Logger, null!);

var translateDiagnosticsService = new RazorTranslateDiagnosticsService(DocumentMappingService, LoggerFactory);
var diagnosticsEndPoint = new RazorPullDiagnosticsEndpoint(LanguageServerFeatureOptions, translateDiagnosticsService, DocumentMappingService, LanguageServer);

var diagnosticsRequest = new VSInternalDocumentDiagnosticsParams
{
TextDocument = new TextDocumentIdentifier { Uri = uri }
};
var diagnostics = await diagnosticsEndPoint.HandleRequestAsync(diagnosticsRequest, requestContext, DisposalToken);

var actual = diagnostics!.First().Diagnostics!;
Assert.NotEmpty(actual);

// Because the test razor project isn't set up properly, we get some extra diagnostics that we don't care about
// so lets just validate that we get the ones we expect. We're testing the communication and translation between
// Razor and C# after all, not whether our test infra can create a fully working project with all references.
foreach (var (code, span) in spans)
{
// If any future test requires multiple diagnostics of the same type, please change this code :)
var diagnostic = Assert.Single(actual, d => d.Code == code);
Assert.Equal(span.First(), diagnostic.Range.AsTextSpan(sourceText));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Microsoft.AspNetCore.Razor.LanguageServer.CodeActions;
using Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Models;
using Microsoft.AspNetCore.Razor.LanguageServer.Common;
using Microsoft.AspNetCore.Razor.LanguageServer.Diagnostics;
using Microsoft.AspNetCore.Razor.LanguageServer.EndpointContracts;
using Microsoft.AspNetCore.Razor.LanguageServer.Protocol;
using Microsoft.AspNetCore.Razor.LanguageServer.Test.Common;
Expand Down Expand Up @@ -113,12 +114,33 @@ public async override Task<TResponse> SendRequestAsync<TParams, TResponse>(strin
RazorLanguageServerCustomMessageTargets.RazorReferencesEndpointName => await HandleReferencesAsync(@params),
RazorLanguageServerCustomMessageTargets.RazorProvideCodeActionsEndpoint => await HandleProvideCodeActionsAsync(@params),
RazorLanguageServerCustomMessageTargets.RazorResolveCodeActionsEndpoint => await HandleResolveCodeActionsAsync(@params),
RazorLanguageServerCustomMessageTargets.RazorPullDiagnosticEndpointName => await HandlePullDiagnosticsAsync(@params),
_ => throw new NotImplementedException($"I don't know how to handle the '{method}' method.")
};

return (TResponse)result;
}

private async Task<RazorPullDiagnosticResponse> HandlePullDiagnosticsAsync<TParams>(TParams @params)
{
Assert.IsType<DelegatedDiagnosticParams>(@params);

var delegatedRequest = new VSInternalDocumentDiagnosticsParams
{
TextDocument = new TextDocumentIdentifier
{
Uri = _csharpDocumentUri,
},
};

var result = await _csharpServer.ExecuteRequestAsync<VSInternalDocumentDiagnosticsParams, VSInternalDiagnosticReport[]>(
VSInternalMethods.DocumentPullDiagnosticName,
delegatedRequest,
_cancellationToken);

return new RazorPullDiagnosticResponse(result, Array.Empty<VSInternalDiagnosticReport>());
}

private async Task<VSInternalCodeAction> HandleResolveCodeActionsAsync<TParams>(TParams @params)
{
var delegatedParams = Assert.IsType<RazorResolveCodeActionParams>(@params);
Expand Down