зеркало из https://github.com/dotnet/razor.git
Merge pull request #6785 from davidwengier/FullAddUsingSupport
This commit is contained in:
Коммит
a8a8f4ad36
|
@ -2,15 +2,72 @@
|
|||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Models;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.Common;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.CodeActions
|
||||
{
|
||||
internal static class AddUsingsCodeActionProviderHelper
|
||||
{
|
||||
public static async Task<TextEdit[]> GetUsingStatementEditsAsync(RazorCodeDocument codeDocument, SourceText originalCSharpText, SourceText changedCSharpText, CancellationToken cancellationToken)
|
||||
{
|
||||
// Now that we're done with everything, lets see if there are any using statements to fix up
|
||||
// We do this by comparing the original generated C# code, and the changed C# code, and look for a difference
|
||||
// in using statements. We can't use edits for this for two main reasons:
|
||||
//
|
||||
// 1. Using statements in the generated code might come from _Imports.razor, or from this file, and C# will shove them anywhere
|
||||
// 2. The edit might not be clean. eg given:
|
||||
// using System;
|
||||
// using System.Text;
|
||||
// Adding "using System.Linq;" could result in an insert of "Linq;\r\nusing System." on line 2
|
||||
//
|
||||
// So because of the above, we look for a difference in C# using directive nodes directly from the C# syntax tree, and apply them manually
|
||||
// to the Razor document.
|
||||
|
||||
var oldUsings = await FindUsingDirectiveStringsAsync(originalCSharpText, cancellationToken);
|
||||
var newUsings = await FindUsingDirectiveStringsAsync(changedCSharpText, cancellationToken);
|
||||
|
||||
var edits = new List<TextEdit>();
|
||||
foreach (var usingStatement in newUsings.Except(oldUsings))
|
||||
{
|
||||
// This identifier will be eventually thrown away.
|
||||
var identifier = new OptionalVersionedTextDocumentIdentifier { Uri = new Uri(codeDocument.Source.FilePath, UriKind.Relative) };
|
||||
var workspaceEdit = AddUsingsCodeActionResolver.CreateAddUsingWorkspaceEdit(usingStatement, codeDocument, codeDocumentIdentifier: identifier);
|
||||
edits.AddRange(workspaceEdit.DocumentChanges!.Value.First.First().Edits);
|
||||
}
|
||||
|
||||
return edits.ToArray();
|
||||
}
|
||||
|
||||
private static async Task<IEnumerable<string>> FindUsingDirectiveStringsAsync(SourceText originalCSharpText, CancellationToken cancellationToken)
|
||||
{
|
||||
var syntaxTree = CSharpSyntaxTree.ParseText(originalCSharpText, cancellationToken: cancellationToken);
|
||||
var syntaxRoot = await syntaxTree.GetRootAsync(cancellationToken);
|
||||
|
||||
// We descend any compilation unit (ie, the file) or and namespaces because the compiler puts all usings inside
|
||||
// the namespace node.
|
||||
var usings = syntaxRoot.DescendantNodes(n => n is BaseNamespaceDeclarationSyntax or CompilationUnitSyntax)
|
||||
// Filter to using directives
|
||||
.OfType<UsingDirectiveSyntax>()
|
||||
// Select everything after the initial "using " part of the statement. This is slightly lazy, for sure, but has
|
||||
// the advantage of us not caring about chagnes to C# syntax, we just grab whatever Roslyn wanted to put in, so
|
||||
// we should still work in C# v26
|
||||
.Select(u => u.ToString().Substring("using ".Length));
|
||||
|
||||
return usings;
|
||||
}
|
||||
|
||||
internal static readonly Regex AddUsingVSCodeAction = new Regex("^@?using ([^;]+);?$", RegexOptions.Compiled, TimeSpan.FromSeconds(1));
|
||||
|
||||
// Internal for testing
|
||||
|
|
|
@ -2,11 +2,15 @@
|
|||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Models;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.Common;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.Common.Extensions;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.Extensions;
|
||||
using Microsoft.CodeAnalysis.Razor.Workspaces.Extensions;
|
||||
using Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.CodeActions
|
||||
|
@ -46,9 +50,20 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.CodeActions
|
|||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (!AddUsingsCodeActionProviderHelper.TryExtractNamespace(codeAction.Title, out var @namespace))
|
||||
var resolvedCodeAction = await ResolveCodeActionWithServerAsync(csharpParams.RazorFileUri, codeAction, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// TODO: Move this higher, so it happens on any code action.
|
||||
// For that though, we need a deeper understanding of applying workspace edits to documents, rather than
|
||||
// just picking out the first one because we assume thats where it will be.
|
||||
// Tracked by https://github.com/dotnet/razor-tooling/issues/6159
|
||||
if (resolvedCodeAction?.Edit?.TryGetDocumentChanges(out var documentChanges) != true)
|
||||
{
|
||||
// Invalid text edit, missing namespace
|
||||
return codeAction;
|
||||
}
|
||||
|
||||
if (documentChanges!.Length != 1)
|
||||
{
|
||||
Debug.Fail("We don't yet support multi-document code actions! If you're seeing this, something about Roslyn changed and we should react.");
|
||||
return codeAction;
|
||||
}
|
||||
|
||||
|
@ -64,14 +79,28 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.CodeActions
|
|||
return codeAction;
|
||||
}
|
||||
|
||||
var codeDocumentIdentifier = new OptionalVersionedTextDocumentIdentifier()
|
||||
{
|
||||
Uri = csharpParams.RazorFileUri,
|
||||
Version = documentContext.Version
|
||||
};
|
||||
var csharpText = codeDocument.GetCSharpSourceText();
|
||||
var edits = documentChanges[0].Edits;
|
||||
var changes = edits.Select(e => e.AsTextChange(csharpText));
|
||||
var changedText = csharpText.WithChanges(changes);
|
||||
|
||||
var edit = AddUsingsCodeActionResolver.CreateAddUsingWorkspaceEdit(@namespace, codeDocument, codeDocumentIdentifier);
|
||||
codeAction.Edit = edit;
|
||||
edits = await AddUsingsCodeActionProviderHelper.GetUsingStatementEditsAsync(codeDocument, csharpText, changedText, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
codeAction.Edit = new WorkspaceEdit
|
||||
{
|
||||
DocumentChanges = new TextDocumentEdit[]
|
||||
{
|
||||
new TextDocumentEdit
|
||||
{
|
||||
TextDocument = new OptionalVersionedTextDocumentIdentifier()
|
||||
{
|
||||
Uri = csharpParams.RazorFileUri,
|
||||
Version = documentContext.Version
|
||||
},
|
||||
Edits = edits
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return codeAction;
|
||||
}
|
||||
|
|
|
@ -429,7 +429,7 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer
|
|||
|
||||
public async override Task<WorkspaceEdit> RemapWorkspaceEditAsync(WorkspaceEdit workspaceEdit, CancellationToken cancellationToken)
|
||||
{
|
||||
if (TryGetDocumentChanges(workspaceEdit, out var documentChanges))
|
||||
if (workspaceEdit.TryGetDocumentChanges(out var documentChanges))
|
||||
{
|
||||
// The LSP spec says, we should prefer `DocumentChanges` property over `Changes` if available.
|
||||
var remappedEdits = await RemapVersionedDocumentEditsAsync(documentChanges, cancellationToken).ConfigureAwait(false);
|
||||
|
@ -794,36 +794,6 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer
|
|||
}
|
||||
}
|
||||
|
||||
private static bool TryGetDocumentChanges(WorkspaceEdit workspaceEdit, [NotNullWhen(true)] out TextDocumentEdit[]? documentChanges)
|
||||
{
|
||||
if (workspaceEdit.DocumentChanges?.Value is TextDocumentEdit[] documentEdits)
|
||||
{
|
||||
documentChanges = documentEdits;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (workspaceEdit.DocumentChanges?.Value is SumType<TextDocumentEdit, CreateFile, RenameFile, DeleteFile>[] sumTypeArray)
|
||||
{
|
||||
var documentEditList = new List<TextDocumentEdit>();
|
||||
foreach (var sumType in sumTypeArray)
|
||||
{
|
||||
if (sumType.Value is TextDocumentEdit textDocumentEdit)
|
||||
{
|
||||
documentEditList.Add(textDocumentEdit);
|
||||
}
|
||||
}
|
||||
|
||||
if (documentEditList.Count > 0)
|
||||
{
|
||||
documentChanges = documentEditList.ToArray();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
documentChanges = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task<TextDocumentEdit[]> RemapVersionedDocumentEditsAsync(TextDocumentEdit[] documentEdits, CancellationToken cancellationToken)
|
||||
{
|
||||
var remappedDocumentEdits = new List<TextDocumentEdit>();
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Extensions
|
||||
{
|
||||
internal static class WorkspaceEditExtensions
|
||||
{
|
||||
public static bool TryGetDocumentChanges(this WorkspaceEdit workspaceEdit, [NotNullWhen(true)] out TextDocumentEdit[]? documentChanges)
|
||||
{
|
||||
if (workspaceEdit.DocumentChanges?.Value is TextDocumentEdit[] documentEdits)
|
||||
{
|
||||
documentChanges = documentEdits;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (workspaceEdit.DocumentChanges?.Value is SumType<TextDocumentEdit, CreateFile, RenameFile, DeleteFile>[] sumTypeArray)
|
||||
{
|
||||
var documentEditList = new List<TextDocumentEdit>();
|
||||
foreach (var sumType in sumTypeArray)
|
||||
{
|
||||
if (sumType.Value is TextDocumentEdit textDocumentEdit)
|
||||
{
|
||||
documentEditList.Add(textDocumentEdit);
|
||||
}
|
||||
}
|
||||
|
||||
if (documentEditList.Count > 0)
|
||||
{
|
||||
documentChanges = documentEditList.ToArray();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
documentChanges = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,8 +16,6 @@ using Microsoft.AspNetCore.Razor.LanguageServer.Common;
|
|||
using Microsoft.AspNetCore.Razor.LanguageServer.Extensions;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.Protocol;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
|
||||
using Microsoft.CodeAnalysis.Razor.Workspaces.Extensions;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
@ -212,51 +210,14 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
|
|||
// Because we need to parse the C# code twice for this operation, lets do a quick check to see if its even necessary
|
||||
if (textEdits.Any(e => e.NewText.IndexOf("using") != -1))
|
||||
{
|
||||
finalEdits = await AddUsingStatementEditsAsync(codeDocument, finalEdits, csharpText, originalTextWithChanges, cancellationToken);
|
||||
var usingStatementEdits = await AddUsingsCodeActionProviderHelper.GetUsingStatementEditsAsync(codeDocument, csharpText, originalTextWithChanges, cancellationToken);
|
||||
finalEdits = usingStatementEdits.Concat(finalEdits).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
return new FormattingResult(finalEdits);
|
||||
}
|
||||
|
||||
private async Task<TextEdit[]> AddUsingStatementEditsAsync(RazorCodeDocument codeDocument, TextEdit[] finalEdits, SourceText csharpText, SourceText originalTextWithChanges, CancellationToken cancellationToken)
|
||||
{
|
||||
// Now that we're done with everything, lets see if there are any using statements to fix up
|
||||
// We do this by comparing the original generated C# code, and the changed C# code, and look for a difference
|
||||
// in using statements. We can't use edits for this for two main reasons:
|
||||
//
|
||||
// 1. Using statements in the generated code might come from _Imports.razor, or from this file, and C# will shove them anywhere
|
||||
// 2. The edit might not be clean. eg given:
|
||||
// using System;
|
||||
// using System.Text;
|
||||
// Adding "using System.Linq;" could result in an insert of "Linq;\r\nusing System." on line 2
|
||||
//
|
||||
// So because of the above, we look for a difference in C# using directive nodes directly from the C# syntax tree, and apply them manually
|
||||
// to the Razor document.
|
||||
|
||||
// First grab the old usings. We just convert them all to strings, because we only care about how the statements are represented in code.
|
||||
var oldSyntaxTree = CSharpSyntaxTree.ParseText(csharpText, cancellationToken: cancellationToken);
|
||||
var oldRoot = await oldSyntaxTree.GetRootAsync(cancellationToken);
|
||||
var oldUsings = oldRoot.DescendantNodes(n => n is BaseNamespaceDeclarationSyntax or CompilationUnitSyntax).OfType<UsingDirectiveSyntax>().Select(u => u.ToString().Substring(6));
|
||||
|
||||
// Grab the new usings
|
||||
var newSyntaxTree = CSharpSyntaxTree.ParseText(originalTextWithChanges, cancellationToken: cancellationToken);
|
||||
var newRoot = await newSyntaxTree.GetRootAsync(cancellationToken);
|
||||
var newUsings = newRoot.DescendantNodes(n => n is BaseNamespaceDeclarationSyntax or CompilationUnitSyntax).OfType<UsingDirectiveSyntax>().Select(u => u.ToString().Substring(6));
|
||||
|
||||
var edits = new List<TextEdit>();
|
||||
foreach (var usingStatement in newUsings.Except(oldUsings))
|
||||
{
|
||||
// This identifier will be eventually thrown away.
|
||||
var identifier = new OptionalVersionedTextDocumentIdentifier { Uri = new Uri(codeDocument.Source.FilePath, UriKind.Relative) };
|
||||
var workspaceEdit = AddUsingsCodeActionResolver.CreateAddUsingWorkspaceEdit(usingStatement, codeDocument, codeDocumentIdentifier: identifier);
|
||||
edits.AddRange(workspaceEdit.DocumentChanges!.Value.First.First().Edits);
|
||||
}
|
||||
|
||||
edits.AddRange(finalEdits);
|
||||
return edits.ToArray();
|
||||
}
|
||||
|
||||
// Returns the minimal TextSpan that encompasses all the differences between the old and the new text.
|
||||
private static SourceText ApplyChangesAndTrackChange(SourceText oldText, IEnumerable<TextChange> changes, out TextSpan spanBeforeChange, out TextSpan spanAfterChange)
|
||||
{
|
||||
|
|
|
@ -4,41 +4,25 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Extensions;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Models;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.Common;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.Extensions;
|
||||
using Microsoft.AspNetCore.Razor.Test.Common;
|
||||
using Microsoft.VisualStudio.LanguageServer.Protocol;
|
||||
using Moq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OmniSharp.Extensions.JsonRpc;
|
||||
using Xunit;
|
||||
using Range = Microsoft.VisualStudio.LanguageServer.Protocol.Range;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.CodeActions
|
||||
{
|
||||
public class AddUsingsCSharpCodeActionResolverTest : LanguageServerTestBase
|
||||
{
|
||||
private static readonly CodeAction s_defaultResolvedCodeAction = new CodeAction()
|
||||
{
|
||||
Title = "@using System.Net",
|
||||
Data = null,
|
||||
Edit = new WorkspaceEdit()
|
||||
{
|
||||
DocumentChanges = new TextDocumentEdit[] {
|
||||
new TextDocumentEdit()
|
||||
{
|
||||
Edits = new TextEdit[] {
|
||||
new TextEdit()
|
||||
{
|
||||
NewText = "using System.Net;"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private static readonly CodeAction s_defaultUnresolvedCodeAction = new CodeAction()
|
||||
{
|
||||
Title = "@using System.Net"
|
||||
|
@ -48,25 +32,139 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.CodeActions
|
|||
public async Task ResolveAsync_ReturnsResolvedCodeAction()
|
||||
{
|
||||
// Arrange
|
||||
CreateCodeActionResolver(out var codeActionParams, out var csharpCodeActionResolver);
|
||||
var resolvedCodeAction = new CodeAction()
|
||||
{
|
||||
Title = "@using System.Net",
|
||||
Data = null,
|
||||
Edit = new WorkspaceEdit()
|
||||
{
|
||||
DocumentChanges = new TextDocumentEdit[] {
|
||||
new TextDocumentEdit()
|
||||
{
|
||||
Edits = new TextEdit[] {
|
||||
new TextEdit()
|
||||
{
|
||||
Range = new Range
|
||||
{
|
||||
Start = new Position(0, 0),
|
||||
End = new Position(0, 0)
|
||||
},
|
||||
NewText = "using System.Net;"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
CreateCodeActionResolver(out var codeActionParams, out var csharpCodeActionResolver, resolvedCodeAction);
|
||||
|
||||
// Act
|
||||
var returnedCodeAction = await csharpCodeActionResolver.ResolveAsync(codeActionParams, s_defaultUnresolvedCodeAction, default);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(s_defaultResolvedCodeAction.Title, returnedCodeAction.Title);
|
||||
Assert.Equal(s_defaultResolvedCodeAction.Data, returnedCodeAction.Data);
|
||||
Assert.Equal(resolvedCodeAction.Title, returnedCodeAction.Title);
|
||||
Assert.Equal(resolvedCodeAction.Data, returnedCodeAction.Data);
|
||||
|
||||
Assert.Equal(1, returnedCodeAction.Edit.DocumentChanges.Value.Count());
|
||||
var returnedEdits = returnedCodeAction.Edit.DocumentChanges.Value.First();
|
||||
Assert.True(returnedEdits.TryGetFirst(out var textDocumentEdit));
|
||||
var returnedTextDocumentEdit = Assert.Single(textDocumentEdit.Edits);
|
||||
Assert.Equal($"@using System.Net{Environment.NewLine}", returnedTextDocumentEdit.NewText);
|
||||
Assert.Equal($"@using System.Net;{Environment.NewLine}", returnedTextDocumentEdit.NewText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResolveAsync_FragmentedEdit_ReturnsResolvedCodeAction()
|
||||
{
|
||||
// Arrange
|
||||
var resolvedCodeAction = new CodeAction()
|
||||
{
|
||||
Title = "@using System.Net",
|
||||
Data = null,
|
||||
Edit = new WorkspaceEdit()
|
||||
{
|
||||
DocumentChanges = new TextDocumentEdit[] {
|
||||
new TextDocumentEdit()
|
||||
{
|
||||
Edits = new TextEdit[] {
|
||||
new TextEdit()
|
||||
{
|
||||
Range = new Range
|
||||
{
|
||||
// This puts it just after another "using" keyword
|
||||
Start = new Position(8, 9),
|
||||
End = new Position(8, 9)
|
||||
},
|
||||
NewText = " System.Net;\r\n using"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
CreateCodeActionResolver(out var codeActionParams, out var csharpCodeActionResolver, resolvedCodeAction);
|
||||
|
||||
// Act
|
||||
var returnedCodeAction = await csharpCodeActionResolver.ResolveAsync(codeActionParams, s_defaultUnresolvedCodeAction, default);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(resolvedCodeAction.Title, returnedCodeAction.Title);
|
||||
Assert.Equal(resolvedCodeAction.Data, returnedCodeAction.Data);
|
||||
|
||||
Assert.Equal(1, returnedCodeAction.Edit.DocumentChanges.Value.Count());
|
||||
var returnedEdits = returnedCodeAction.Edit.DocumentChanges.Value.First();
|
||||
Assert.True(returnedEdits.TryGetFirst(out var textDocumentEdit));
|
||||
var returnedTextDocumentEdit = Assert.Single(textDocumentEdit.Edits);
|
||||
Assert.Equal($"@using System.Net;{Environment.NewLine}", returnedTextDocumentEdit.NewText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResolveAsync_GlobalUsing_ReturnsResolvedCodeAction()
|
||||
{
|
||||
// Arrange
|
||||
var resolvedCodeAction = new CodeAction()
|
||||
{
|
||||
Title = "@using System.Net",
|
||||
Data = null,
|
||||
Edit = new WorkspaceEdit()
|
||||
{
|
||||
DocumentChanges = new TextDocumentEdit[] {
|
||||
new TextDocumentEdit()
|
||||
{
|
||||
Edits = new TextEdit[] {
|
||||
new TextEdit()
|
||||
{
|
||||
Range = new Range
|
||||
{
|
||||
Start = new Position(0, 0),
|
||||
End = new Position(0, 0)
|
||||
},
|
||||
NewText = "using global::System.Net;"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
CreateCodeActionResolver(out var codeActionParams, out var csharpCodeActionResolver, resolvedCodeAction);
|
||||
|
||||
// Act
|
||||
var returnedCodeAction = await csharpCodeActionResolver.ResolveAsync(codeActionParams, s_defaultUnresolvedCodeAction, default);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(resolvedCodeAction.Title, returnedCodeAction.Title);
|
||||
Assert.Equal(resolvedCodeAction.Data, returnedCodeAction.Data);
|
||||
|
||||
Assert.Equal(1, returnedCodeAction.Edit.DocumentChanges.Value.Count());
|
||||
var returnedEdits = returnedCodeAction.Edit.DocumentChanges.Value.First();
|
||||
Assert.True(returnedEdits.TryGetFirst(out var textDocumentEdit));
|
||||
var returnedTextDocumentEdit = Assert.Single(textDocumentEdit.Edits);
|
||||
Assert.Equal($"@using global::System.Net;{Environment.NewLine}", returnedTextDocumentEdit.NewText);
|
||||
}
|
||||
|
||||
private void CreateCodeActionResolver(
|
||||
out CSharpCodeActionParams codeActionParams,
|
||||
out AddUsingsCSharpCodeActionResolver addUsingResolver)
|
||||
out AddUsingsCSharpCodeActionResolver addUsingResolver,
|
||||
CodeAction resolvedCodeAction)
|
||||
{
|
||||
var documentUri = new Uri("c:/Test.razor");
|
||||
var contents = string.Empty;
|
||||
|
@ -78,16 +176,24 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.CodeActions
|
|||
RazorFileUri = documentUri
|
||||
};
|
||||
|
||||
var languageServer = CreateLanguageServer();
|
||||
var languageServer = CreateLanguageServer(resolvedCodeAction);
|
||||
|
||||
addUsingResolver = new AddUsingsCSharpCodeActionResolver(
|
||||
CreateDocumentContextFactory(documentUri, codeDocument),
|
||||
languageServer);
|
||||
}
|
||||
|
||||
private static ClientNotifierServiceBase CreateLanguageServer()
|
||||
private static ClientNotifierServiceBase CreateLanguageServer(CodeAction resolvedCodeAction)
|
||||
{
|
||||
var responseRouterReturns = new Mock<IResponseRouterReturns>(MockBehavior.Strict);
|
||||
responseRouterReturns
|
||||
.Setup(l => l.Returning<CodeAction>(It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.FromResult(resolvedCodeAction));
|
||||
|
||||
var languageServer = new Mock<ClientNotifierServiceBase>(MockBehavior.Strict);
|
||||
languageServer
|
||||
.Setup(l => l.SendRequestAsync(RazorLanguageServerCustomMessageTargets.RazorResolveCodeActionsEndpoint, It.IsAny<RazorResolveCodeActionParams>()))
|
||||
.Returns(Task.FromResult(responseRouterReturns.Object));
|
||||
|
||||
return languageServer.Object;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче