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);