diff --git a/eng/Versions.props b/eng/Versions.props
index 94649d377c6..d3d81f6cb7a 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -77,7 +77,7 @@
1.1.2-beta1.22512.1
17.5.0-preview-2-33117-317
17.5.274-preview
- 4.6.0-1.23101.17
+ 4.6.0-2.23113.15
17.6.4-preview
4.4.0
$(RoslynPackageVersion)
diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test.Common/CSharpTestLspServerHelpers.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test.Common/CSharpTestLspServerHelpers.cs
index 401da4317c3..2106d8f02c8 100644
--- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test.Common/CSharpTestLspServerHelpers.cs
+++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test.Common/CSharpTestLspServerHelpers.cs
@@ -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;
@@ -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();
@@ -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();
+ 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.InitializeDiagnosticsServices(workspace);
+ }
+
private record CSharpFile(Uri DocumentUri, SourceText CSharpSourceText);
private class EmptyMappingService : IRazorSpanMappingService
diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test.Common/TestRazorDocumentServiceProvider.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test.Common/TestRazorDocumentServiceProvider.cs
index 8493a8964f1..09be1efaa42 100644
--- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test.Common/TestRazorDocumentServiceProvider.cs
+++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test.Common/TestRazorDocumentServiceProvider.cs
@@ -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()
{
diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Diagnostics/CSharpDiagnosticsEndToEndTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Diagnostics/CSharpDiagnosticsEndToEndTest.cs
new file mode 100644
index 00000000000..b5d362682d6
--- /dev/null
+++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Diagnostics/CSharpDiagnosticsEndToEndTest.cs
@@ -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 = """
+
+
+
+ @functions
+ {
+ public void M()
+ {
+ {|CS0103:CallOnMe|}();
+ }
+ }
+
+ """;
+
+ await ValidateDiagnosticsAsync(input);
+ }
+
+ private async Task ValidateDiagnosticsAsync(string input)
+ {
+ TestFileMarkupParser.GetSpans(input, out input, out ImmutableDictionary> 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));
+ }
+ }
+}
diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/SingleServerDelegatingEndpointTestBase.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/SingleServerDelegatingEndpointTestBase.cs
index ac59721ddb2..cba393fa431 100644
--- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/SingleServerDelegatingEndpointTestBase.cs
+++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/SingleServerDelegatingEndpointTestBase.cs
@@ -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;
@@ -113,12 +114,33 @@ public async override Task SendRequestAsync(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 HandlePullDiagnosticsAsync(TParams @params)
+ {
+ Assert.IsType(@params);
+
+ var delegatedRequest = new VSInternalDocumentDiagnosticsParams
+ {
+ TextDocument = new TextDocumentIdentifier
+ {
+ Uri = _csharpDocumentUri,
+ },
+ };
+
+ var result = await _csharpServer.ExecuteRequestAsync(
+ VSInternalMethods.DocumentPullDiagnosticName,
+ delegatedRequest,
+ _cancellationToken);
+
+ return new RazorPullDiagnosticResponse(result, Array.Empty());
+ }
+
private async Task HandleResolveCodeActionsAsync(TParams @params)
{
var delegatedParams = Assert.IsType(@params);