From 64b0da1e680fd29a2876dab3425b13a119ada930 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Fri, 8 Aug 2025 11:58:45 +1000 Subject: [PATCH 1/3] Add failing test --- .../Cohost/CohostRoslynRenameTest.cs | 117 ++++++++++++++++-- 1 file changed, 108 insertions(+), 9 deletions(-) diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostRoslynRenameTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostRoslynRenameTest.cs index 48cbf70b466..2f61c34b934 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostRoslynRenameTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostRoslynRenameTest.cs @@ -17,9 +17,12 @@ using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers; using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor.CohostingShared; +using Microsoft.CodeAnalysis.Razor.DocumentMapping; using Microsoft.CodeAnalysis.Razor.Remote; +using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Rename; using Microsoft.CodeAnalysis.Text; +using Microsoft.NET.Sdk.Razor.SourceGenerators; using Roslyn.Test.Utilities; using Xunit; using Xunit.Abstractions; @@ -44,7 +47,7 @@ public string MyMethod() razorFile: """ This is a Razor document. -

@_myClass.MyMethod()

+

@_myClass.MyM$$ethod()

@code { @@ -58,7 +61,6 @@ public string M() The end. """, - symbolToRename: "MyMethod", newName: "CallThisFunction", expectedCSharpFile: """ public class MyClass @@ -88,6 +90,65 @@ The end. """, useLsp); + [Theory] + [CombinatorialData] + public Task CSharp_Local(bool useLsp) + => VerifyRenamesAsync( + csharpFile: """ + public class MyClass + { + public string MyMethod() + { + return $"Hi from {nameof(MyMethod)}"; + } + } + """, + razorFile: """ + This is a Razor document. + + @{var someV$$ariable = true;} @someVariable + + @code + { + private MyClass _myClass = new MyClass(); + + public string M() + { + return _myClass.MyMethod(); + } + } + + The end. + """, + newName: "fishPants", + expectedCSharpFile: """ + public class MyClass + { + public string MyMethod() + { + return $"Hi from {nameof(MyMethod)}"; + } + } + """, + expectedRazorFile: """ + This is a Razor document. + + @{var fishPants = true;} @fishPants + + @code + { + private MyClass _myClass = new MyClass(); + + public string M() + { + return _myClass.MyMethod(); + } + } + + The end. + """, + useLsp); + private protected override TestComposition ConfigureRoslynDevenvComposition(TestComposition composition) { return composition @@ -97,30 +158,64 @@ private protected override TestComposition ConfigureRoslynDevenvComposition(Test private async Task VerifyRenamesAsync( string csharpFile, - string razorFile, - string symbolToRename, + TestCode razorFile, string newName, string expectedCSharpFile, string expectedRazorFile, bool useLsp) { - var razorDocument = CreateProjectAndRazorDocument(razorFile, additionalFiles: [(Path.Combine(TestProjectData.SomeProjectPath, "File.cs"), csharpFile)], createSeparateRemoteAndLocalWorkspaces: true); + var razorDocument = CreateProjectAndRazorDocument(razorFile.Text, additionalFiles: [(Path.Combine(TestProjectData.SomeProjectPath, "File.cs"), csharpFile)], createSeparateRemoteAndLocalWorkspaces: true); var project = razorDocument.Project; var csharpDocument = project.Documents.First(); var compilation = await project.GetCompilationAsync(DisposalToken); - var symbol = compilation.AssumeNotNull().GetSymbolsWithName(symbolToRename).First(); + Assert.True(razorDocument.TryComputeHintNameFromRazorDocument(out var hintName)); + var generatedDocument = await project.TryGetSourceGeneratedDocumentFromHintNameAsync(hintName, DisposalToken); + + var node = await GetSyntaxNodeAsync(generatedDocument.AssumeNotNull(), razorFile.Position, razorDocument); + if (useLsp) { - await VerifyLspRenameAsync(newName, expectedCSharpFile, expectedRazorFile, razorDocument, csharpDocument, symbol); + await VerifyLspRenameAsync(newName, expectedCSharpFile, expectedRazorFile, razorDocument, csharpDocument, node); } else { + var symbol = FindSymbolToRename(compilation.AssumeNotNull(), node); await VerifyVSRenameAsync(newName, expectedCSharpFile, expectedRazorFile, razorDocument, project, csharpDocument, symbol); } } + private ISymbol FindSymbolToRename(Compilation compilation, SyntaxNode node) + { + var semanticModel = compilation.GetSemanticModel(node.SyntaxTree); + var symbol = semanticModel.GetDeclaredSymbol(node, DisposalToken); + if (symbol is null) + { + symbol = semanticModel.GetSymbolInfo(node, DisposalToken).Symbol; + } + + Assert.NotNull(symbol); + return symbol; + } + + private async Task GetSyntaxNodeAsync(Document document, int position, TextDocument razorDocument) + { + var snapshotManager = OOPExportProvider.GetExportedValue(); + var documentMappingService = OOPExportProvider.GetExportedValue(); + var snapshot = snapshotManager.GetSnapshot(razorDocument); + var codeDocument = await snapshot.GetGeneratedOutputAsync(DisposalToken); + + Assert.True(documentMappingService.TryMapToCSharpDocumentPosition(codeDocument.GetCSharpDocument().AssumeNotNull(), position, out var csharpPosition, out _)); + + var sourceText = await document.GetTextAsync(DisposalToken); + var span = sourceText.GetTextSpan(csharpPosition, csharpPosition); + var tree = await document.AssumeNotNull().GetSyntaxTreeAsync(DisposalToken); + var root = await tree.AssumeNotNull().GetRootAsync(DisposalToken); + + return root.FindNode(span); + } + private async Task VerifyVSRenameAsync(string newName, string expectedCSharpFile, string expectedRazorFile, TextDocument razorDocument, Project project, Document csharpDocument, ISymbol symbol) { var solution = await Renamer.RenameSymbolAsync(project.Solution, symbol, new SymbolRenameOptions(), newName, DisposalToken); @@ -155,13 +250,17 @@ private async Task VerifyVSRenameAsync(string newName, string expectedCSharpFile AssertEx.EqualOrDiff(expectedRazorFile, razorText.ToString()); } - private async Task VerifyLspRenameAsync(string newName, string expectedCSharpFile, string expectedRazorFile, TextDocument razorDocument, Document csharpDocument, ISymbol symbol) + private async Task VerifyLspRenameAsync(string newName, string expectedCSharpFile, string expectedRazorFile, TextDocument razorDocument, Document csharpDocument, SyntaxNode node) { // Normally in cohosting tests we directly construct and invoke the endpoints, but in this scenario Roslyn is going to do it // using a service in their MEF composition, so we have to jump through an extra hook to hook up our test invoker. var invoker = RoslynDevenvExportProvider.AssumeNotNull().GetExportedValue(); invoker.SetInvoker(RemoteServiceInvoker); - var workspaceEdit = await Rename.GetRenameEditAsync(csharpDocument, symbol.Locations.First().GetLineSpan().StartLinePosition, newName, DisposalToken); + + var tree = node.SyntaxTree; + var documentToRename = razorDocument.Project.Solution.GetDocument(tree).AssumeNotNull(); + var linePosition = node.SyntaxTree.GetText().GetLinePosition(node.SpanStart); + var workspaceEdit = await Rename.GetRenameEditAsync(documentToRename, linePosition, newName, DisposalToken); Assert.NotNull(workspaceEdit); var csharpSourceText = await csharpDocument.GetTextAsync(DisposalToken); From ff2c084e7b06afc965e2dab778f2739cfd76c331 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 13 Aug 2025 14:24:39 +1000 Subject: [PATCH 2/3] Fix application of edits --- .../Cohost/CohostRoslynRenameTest.cs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostRoslynRenameTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostRoslynRenameTest.cs index 2f61c34b934..b3d86da7fd7 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostRoslynRenameTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostRoslynRenameTest.cs @@ -22,7 +22,6 @@ using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Rename; using Microsoft.CodeAnalysis.Text; -using Microsoft.NET.Sdk.Razor.SourceGenerators; using Roslyn.Test.Utilities; using Xunit; using Xunit.Abstractions; @@ -275,16 +274,11 @@ private async Task VerifyLspRenameAsync(string newName, string expectedCSharpFil private static string ApplyDocumentEdits(SourceText inputText, Uri documentUri, WorkspaceEdit result) { Assert.True(result.TryGetTextDocumentEdits(out var textDocumentEdits)); - foreach (var textDocumentEdit in textDocumentEdits) - { - if (textDocumentEdit.TextDocument.DocumentUri.GetRequiredParsedUri() == documentUri) - { - foreach (var edit in textDocumentEdit.Edits) - { - inputText = inputText.WithChanges(inputText.GetTextChange((TextEdit)edit)); - } - } - } + var changes = textDocumentEdits + .Where(e => e.TextDocument.DocumentUri.GetRequiredParsedUri() == documentUri) + .SelectMany(e => e.Edits) + .Select(e => inputText.GetTextChange((TextEdit)e)); + inputText = inputText.WithChanges(changes); return inputText.ToString(); } From 46fb621e817f5e01fb228bcee0c70f3ee876fb1b Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 13 Aug 2025 14:24:54 +1000 Subject: [PATCH 3/3] Bump Roslyn and ignore obsolete warnings for now --- eng/Version.Details.props | 42 +++++----- eng/Version.Details.xml | 84 +++++++++---------- .../CohostStartupService.cs | 4 + .../Completion/RemoteCompletionService.cs | 4 + .../InlayHints/RemoteInlayHintService.cs | 4 + 5 files changed, 75 insertions(+), 63 deletions(-) diff --git a/eng/Version.Details.props b/eng/Version.Details.props index 1966e0ef10d..54e03d60121 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -7,27 +7,27 @@ This file should be imported by eng/Versions.props - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 - 5.0.0-2.25380.11 + 5.0.0-2.25412.7 + 5.0.0-2.25412.7 + 5.0.0-2.25412.7 + 5.0.0-2.25412.7 + 5.0.0-2.25412.7 + 5.0.0-2.25412.7 + 5.0.0-2.25412.7 + 5.0.0-2.25412.7 + 5.0.0-2.25412.7 + 5.0.0-2.25412.7 + 5.0.0-2.25412.7 + 5.0.0-2.25412.7 + 5.0.0-2.25412.7 + 5.0.0-2.25412.7 + 5.0.0-2.25412.7 + 5.0.0-2.25412.7 + 5.0.0-2.25412.7 + 5.0.0-2.25412.7 + 5.0.0-2.25412.7 + 5.0.0-2.25412.7 + 5.0.0-2.25412.7 9.0.0-beta.25255.5 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 0165310fb90..aa4e478c200 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -2,89 +2,89 @@ - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + a902000a9416fb2f837ae90553b44828f5099305 - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + a902000a9416fb2f837ae90553b44828f5099305 - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + a902000a9416fb2f837ae90553b44828f5099305 - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + a902000a9416fb2f837ae90553b44828f5099305 - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + a902000a9416fb2f837ae90553b44828f5099305 - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + a902000a9416fb2f837ae90553b44828f5099305 - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + a902000a9416fb2f837ae90553b44828f5099305 - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + a902000a9416fb2f837ae90553b44828f5099305 - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + a902000a9416fb2f837ae90553b44828f5099305 - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + a902000a9416fb2f837ae90553b44828f5099305 - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + a902000a9416fb2f837ae90553b44828f5099305 - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + a902000a9416fb2f837ae90553b44828f5099305 - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + a902000a9416fb2f837ae90553b44828f5099305 - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + a902000a9416fb2f837ae90553b44828f5099305 - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + a902000a9416fb2f837ae90553b44828f5099305 - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + a902000a9416fb2f837ae90553b44828f5099305 - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + a902000a9416fb2f837ae90553b44828f5099305 - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + a902000a9416fb2f837ae90553b44828f5099305 - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + a902000a9416fb2f837ae90553b44828f5099305 - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + a902000a9416fb2f837ae90553b44828f5099305 - + https://github.com/dotnet/roslyn - 512fe5197056a877a86dff3054d8bd0028af13ed + a902000a9416fb2f837ae90553b44828f5099305 diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/CohostStartupService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/CohostStartupService.cs index e9e40205ad0..c5497d1ad87 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/CohostStartupService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/CohostStartupService.cs @@ -14,11 +14,15 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost; +#pragma warning disable CS0618 // Type or member is obsolete. Fixed in https://github.com/dotnet/razor/pull/12079 [Export(typeof(ICohostStartupService))] +#pragma warning restore CS0618 // Type or member is obsolete [method: ImportingConstructor] internal sealed class CohostStartupService( [ImportMany] IEnumerable> lazyStartupServices, +#pragma warning disable CS0618 // Type or member is obsolete. Fixed in https://github.com/dotnet/razor/pull/12079 ILoggerFactory loggerFactory) : ICohostStartupService +#pragma warning restore CS0618 // Type or member is obsolete { private readonly ImmutableArray> _lazyStartupServices = [.. lazyStartupServices]; private readonly ILogger _logger = loggerFactory.GetOrCreateLogger(); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Completion/RemoteCompletionService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Completion/RemoteCompletionService.cs index 48a304b5a52..a61c99c7ee1 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Completion/RemoteCompletionService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Completion/RemoteCompletionService.cs @@ -204,6 +204,7 @@ private async ValueTask GetCompletionAsync( VSInternalCompletionList? completionList = null; using (_telemetryReporter.TrackLspRequest(Methods.TextDocumentCompletionName, Constants.ExternalAccessServerName, TelemetryThresholds.CompletionSubLSPTelemetryThreshold, correlationId)) { +#pragma warning disable CS0618 // Type or member is obsolete. Fixed in https://github.com/dotnet/razor/pull/12079 completionList = await ExternalAccess.Razor.Cohost.Handlers.Completion.GetCompletionListAsync( generatedDocument, mappedLinePosition, @@ -212,6 +213,7 @@ private async ValueTask GetCompletionAsync( completionSetting, cancellationToken) .ConfigureAwait(false); +#pragma warning restore CS0618 // Type or member is obsolete } if (completionList is null) @@ -328,12 +330,14 @@ private async ValueTask ResolveCSharpCompletionItemAsy var clientCapabilities = _clientCapabilitiesService.ClientCapabilities; var completionListSetting = clientCapabilities.TextDocument?.Completion; +#pragma warning disable CS0618 // Type or member is obsolete. Fixed in https://github.com/dotnet/razor/pull/12079 var result = await ExternalAccess.Razor.Cohost.Handlers.Completion.ResolveCompletionItemAsync( request, generatedDocument, clientCapabilities.SupportsVisualStudioExtensions, completionListSetting ?? new(), cancellationToken).ConfigureAwait(false); +#pragma warning restore CS0618 // Type or member is obsolete var item = JsonHelpers.Convert(result).AssumeNotNull(); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/InlayHints/RemoteInlayHintService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/InlayHints/RemoteInlayHintService.cs index 40b123d2fb9..65f3766881a 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/InlayHints/RemoteInlayHintService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/InlayHints/RemoteInlayHintService.cs @@ -67,7 +67,9 @@ protected override IRemoteInlayHintService CreateService(in ServiceArgs args) var textDocument = inlayHintParams.TextDocument.WithUri(generatedDocument.CreateUri()); var range = projectedLinePositionSpan.ToRange(); +#pragma warning disable CS0618 // Type or member is obsolete. Fixed in https://github.com/dotnet/razor/pull/12079 var hints = await InlayHints.GetInlayHintsAsync(generatedDocument, textDocument, range, displayAllOverride, cancellationToken).ConfigureAwait(false); +#pragma warning restore CS0618 // Type or member is obsolete if (hints is null) { @@ -130,6 +132,8 @@ private async ValueTask ResolveInlayHintAsync(RemoteDocumentContext c .GetGeneratedDocumentAsync(cancellationToken) .ConfigureAwait(false); +#pragma warning disable CS0618 // Type or member is obsolete. Fixed in https://github.com/dotnet/razor/pull/12079 return await InlayHints.ResolveInlayHintAsync(generatedDocument, inlayHint, cancellationToken).ConfigureAwait(false); +#pragma warning restore CS0618 // Type or member is obsolete } }