public async Task ResolveAsync_NonTextDocumentEdit_ReturnsOriginalCodeAction() { // Arrange var resolvedCodeAction = new RazorCodeAction() { Title = "ResolvedCodeAction", Data = new object(), Edit = new WorkspaceEdit() { DocumentChanges = new Container <WorkspaceEditDocumentChange>( new WorkspaceEditDocumentChange( new CreateFile() { Uri = new Uri("c:/some/uri.razor") } )) } }; var languageServer = CreateLanguageServer(resolvedCodeAction); CreateCodeActionResolver(out var codeActionParams, out var csharpCodeActionResolver, languageServer: languageServer); // Act var returnedCodeAction = await csharpCodeActionResolver.ResolveAsync(codeActionParams, DefaultUnresolvedCodeAction, default); // Assert Assert.Equal(DefaultUnresolvedCodeAction.Title, returnedCodeAction.Title); }
public async Task Handle_Valid_CSharpCodeAction_WithRazorResolver_ResolvesNull() { // Arrange var codeActionEndpoint = new CodeActionResolutionEndpoint( new RazorCodeActionResolver[] { new MockRazorCodeActionResolver("Test"), }, Array.Empty <CSharpCodeActionResolver>(), LoggerFactory); var requestParams = new RazorCodeActionResolutionParams() { Action = "Test", Language = LanguageServerConstants.CodeActions.Languages.CSharp, Data = JObject.FromObject(new CSharpCodeActionParams()) }; var request = new RazorCodeAction() { Title = "Valid request", Data = JObject.FromObject(requestParams) }; #if DEBUG // Act & Assert (Throws due to debug asserts) await Assert.ThrowsAnyAsync <Exception>(async() => await codeActionEndpoint.Handle(request, default)); #else // Act var resolvedCodeAction = await codeActionEndpoint.Handle(request, default); // Assert Assert.Null(resolvedCodeAction.Edit); #endif }
public async Task ProvideAsync_InvalidCodeActions_ReturnsNoCodeActions() { // Arrange var documentPath = "c:/Test.razor"; var contents = "@code { Path; }"; var request = new CodeActionParams() { TextDocument = new TextDocumentIdentifier(new Uri(documentPath)), Range = new Range(), Context = new CodeActionContext() }; var location = new SourceLocation(8, -1, -1); var context = CreateRazorCodeActionContext(request, location, documentPath, contents, new SourceSpan(8, 4)); context.CodeDocument.SetFileKind(FileKinds.Legacy); var provider = new DefaultCSharpCodeActionProvider(); var codeActions = new RazorCodeAction[] { new RazorCodeAction() { Title = "Do something not really supported in razor", Name = "Non-existant name" } }; // Act var providedCodeActions = await provider.ProvideAsync(context, codeActions, default); // Assert Assert.Empty(providedCodeActions); }
// Internal for testing internal async Task <RazorCodeAction> ResolveCSharpCodeAction( RazorCodeAction codeAction, RazorCodeActionResolutionParams resolutionParams, CancellationToken cancellationToken) { if (!(resolutionParams.Data is JObject csharpParamsObj)) { Debug.Fail($"Invalid CSharp CodeAction Received."); return(codeAction); } var csharpParams = csharpParamsObj.ToObject <CSharpCodeActionParams>(); codeAction.Data = csharpParams.Data; if (!_csharpCodeActionResolvers.TryGetValue(resolutionParams.Action, out var resolver)) { Debug.Fail($"No resolver registered for {GetCodeActionId(resolutionParams)}."); return(codeAction); } var resolvedCodeAction = await resolver.ResolveAsync(csharpParams, codeAction, cancellationToken); return(resolvedCodeAction); }
public async Task Handle_Valid_RazorCodeAction_WithResolver() { // Arrange var codeActionEndpoint = new CodeActionResolutionEndpoint( new RazorCodeActionResolver[] { new MockRazorCodeActionResolver("Test"), }, Array.Empty <CSharpCodeActionResolver>(), LoggerFactory); var requestParams = new RazorCodeActionResolutionParams() { Action = "Test", Language = LanguageServerConstants.CodeActions.Languages.Razor, Data = new AddUsingsCodeActionParams() }; var request = new RazorCodeAction() { Title = "Valid request", Data = JObject.FromObject(requestParams) }; // Act var razorCodeAction = await codeActionEndpoint.Handle(request, default); // Assert Assert.NotNull(razorCodeAction.Edit); }
public async Task ProvideAsync_InvalidCodeActions_ReturnsNoCodeActions() { // Arrange var provider = new DefaultCSharpCodeActionProvider(); var context = CreateCodeActionContext(supportsCodeActionResolve: true); var codeActions = new RazorCodeAction[] { new RazorCodeAction() { Title = "Do something not really supported in razor" }, new RazorCodeAction() { // Invalid regex pattern shouldn't match Title = "Generate constructor 'Counter(int)' xyz" } }; // Act var providedCodeActions = await provider.ProvideAsync(context, codeActions, default); // Assert Assert.Empty(providedCodeActions); }
public async Task Handle_Valid_CSharpCodeAction_WithMultipleLanguageResolvers() { // Arrange var codeActionEndpoint = new CodeActionResolutionEndpoint( new RazorCodeActionResolver[] { new MockRazorCodeActionResolver("TestRazor"), }, new CSharpCodeActionResolver[] { new MockCSharpCodeActionResolver("TestCSharp"), }, LoggerFactory); var requestParams = new RazorCodeActionResolutionParams() { Action = "TestCSharp", Language = LanguageServerConstants.CodeActions.Languages.CSharp, Data = JObject.FromObject(new CSharpCodeActionParams()) }; var request = new RazorCodeAction() { Title = "Valid request", Data = JObject.FromObject(requestParams) }; // Act var razorCodeAction = await codeActionEndpoint.Handle(request, default); // Assert Assert.NotNull(razorCodeAction.Edit); }
protected async Task <RazorCodeAction> ResolveCodeActionWithServerAsync(RazorCodeAction codeAction, CancellationToken cancellationToken) { var response = _languageServer.SendRequest(LanguageServerConstants.RazorResolveCodeActionsEndpoint, codeAction); var resolvedCodeAction = await response.Returning <RazorCodeAction>(cancellationToken); return(resolvedCodeAction); }
private static RazorCodeAction CreateFQNCodeAction( RazorCodeActionContext context, Diagnostic fqnDiagnostic, RazorCodeAction codeAction, string fullyQualifiedName) { var codeDocumentIdentifier = new VersionedTextDocumentIdentifier() { Uri = context.Request.TextDocument.Uri }; var fqnTextEdit = new TextEdit() { NewText = fullyQualifiedName, Range = fqnDiagnostic.Range }; var fqnWorkspaceEditDocumentChange = new WorkspaceEditDocumentChange(new TextDocumentEdit() { TextDocument = codeDocumentIdentifier, Edits = new[] { fqnTextEdit }, }); var fqnWorkspaceEdit = new WorkspaceEdit() { DocumentChanges = new[] { fqnWorkspaceEditDocumentChange } }; return(new RazorCodeAction() { Title = codeAction.Title, Edit = fqnWorkspaceEdit }); }
public async Task ResolveCSharpCodeAction_ResolveMultipleLanguageProviders() { // Arrange var codeActionEndpoint = new CodeActionResolutionEndpoint( new RazorCodeActionResolver[] { new MockRazorNullCodeActionResolver("A"), new MockRazorCodeActionResolver("B"), }, new CSharpCodeActionResolver[] { new MockCSharpNullCodeActionResolver("C"), new MockCSharpCodeActionResolver("D"), }, LoggerFactory); var codeAction = new RazorCodeAction(); var request = new RazorCodeActionResolutionParams() { Action = "D", Language = LanguageServerConstants.CodeActions.Languages.CSharp, Data = JObject.FromObject(new CSharpCodeActionParams()) }; // Act var resolvedCodeAction = await codeActionEndpoint.ResolveCSharpCodeActionAsync(codeAction, request, default); // Assert Assert.NotNull(resolvedCodeAction.Edit); }
// Internal for testing internal async Task <RazorCodeAction> ResolveRazorCodeAction( RazorCodeAction codeAction, RazorCodeActionResolutionParams resolutionParams, CancellationToken cancellationToken) { if (!_razorCodeActionResolvers.TryGetValue(resolutionParams.Action, out var resolver)) { Debug.Fail($"No resolver registered for {GetCodeActionId(resolutionParams)}."); return(codeAction); } codeAction.Edit = await resolver.ResolveAsync(resolutionParams.Data as JObject, cancellationToken).ConfigureAwait(false); return(codeAction); }
private static bool TryProcessCodeAction( RazorCodeActionContext context, RazorCodeAction codeAction, Diagnostic diagnostic, string associatedValue, out ICollection <RazorCodeAction> typeAccessibilityCodeActions) { var fqn = string.Empty; // When there's only one FQN suggestion, code action title is of the form: // `System.Net.Dns` if (!codeAction.Title.Any(c => char.IsWhiteSpace(c)) && codeAction.Title.EndsWith(associatedValue, StringComparison.OrdinalIgnoreCase)) { fqn = codeAction.Title; } else { // When there are multiple FQN suggestions, the code action title is of the form: // `Fully qualify 'Dns' -> System.Net.Dns` var expectedCodeActionPrefix = $"Fully qualify '{associatedValue}' -> "; if (codeAction.Title.StartsWith(expectedCodeActionPrefix, StringComparison.OrdinalIgnoreCase)) { fqn = codeAction.Title.Substring(expectedCodeActionPrefix.Length); } } if (string.IsNullOrEmpty(fqn)) { typeAccessibilityCodeActions = default; return(false); } typeAccessibilityCodeActions = new List <RazorCodeAction>(); var fqnCodeAction = CreateFQNCodeAction(context, diagnostic, codeAction, fqn); typeAccessibilityCodeActions.Add(fqnCodeAction); var addUsingCodeAction = CreateAddUsingCodeAction(context, fqn); if (addUsingCodeAction != null) { typeAccessibilityCodeActions.Add(addUsingCodeAction); } return(true); }
private IClientLanguageServer CreateLanguageServer(RazorCodeAction resolvedCodeAction = null) { var responseRouterReturns = new Mock <IResponseRouterReturns>(MockBehavior.Strict); responseRouterReturns .Setup(l => l.Returning <RazorCodeAction>(It.IsAny <CancellationToken>())) .Returns(Task.FromResult(resolvedCodeAction ?? DefaultResolvedCodeAction)); var languageServer = new Mock <IClientLanguageServer>(MockBehavior.Strict); languageServer .Setup(l => l.SendRequest(LanguageServerConstants.RazorResolveCodeActionsEndpoint, It.IsAny <RazorCodeAction>())) .Returns(responseRouterReturns.Object); return(languageServer.Object); }
public async Task ResolveAsync_MultipleDocumentChanges_ReturnsOriginalCodeAction() { // Arrange var resolvedCodeAction = new RazorCodeAction() { Title = "ResolvedCodeAction", Data = new object(), Edit = new WorkspaceEdit() { DocumentChanges = new Container <WorkspaceEditDocumentChange>( new WorkspaceEditDocumentChange( new TextDocumentEdit() { Edits = new TextEditContainer( new TextEdit() { NewText = "1. Generated C# Based Edit" } ) } ), new WorkspaceEditDocumentChange( new TextDocumentEdit() { Edits = new TextEditContainer( new TextEdit() { NewText = "2. Generated C# Based Edit" } ) } )) } }; var languageServer = CreateLanguageServer(resolvedCodeAction); CreateCodeActionResolver(out var codeActionParams, out var csharpCodeActionResolver, languageServer: languageServer); // Act var returnedCodeAction = await csharpCodeActionResolver.ResolveAsync(codeActionParams, DefaultUnresolvedCodeAction, default); // Assert Assert.Equal(DefaultUnresolvedCodeAction.Title, returnedCodeAction.Title); }
public async Task <RazorCodeAction> Handle(RazorCodeAction request, CancellationToken cancellationToken) { if (request is null) { throw new ArgumentNullException(nameof(request)); } if (!(request.Data is JObject paramsObj)) { Debug.Fail($"Invalid CodeAction Received {request.Title}."); return(request); } var resolutionParams = paramsObj.ToObject <RazorCodeActionResolutionParams>(); request.Edit = await GetWorkspaceEditAsync(resolutionParams, cancellationToken).ConfigureAwait(false); return(request); }
public async Task Handle_Valid_RazorCodeAction_Resolve() { // Arrange var codeActionEndpoint = new CodeActionResolutionEndpoint(new RazorCodeActionResolver[] { new MockCodeActionResolver("Test"), }, LoggerFactory); var requestParams = new RazorCodeActionResolutionParams() { Action = "Test", Data = new AddUsingsCodeActionParams() }; var request = new RazorCodeAction() { Title = "Valid request", Data = JObject.FromObject(requestParams) }; // Act var razorCodeAction = await codeActionEndpoint.Handle(request, default); // Assert Assert.NotNull(razorCodeAction.Edit); }
public async Task ResolveAsync_NoDocumentChanges_ReturnsOriginalCodeAction() { // Arrange var resolvedCodeAction = new RazorCodeAction() { Title = "ResolvedCodeAction", Data = new object(), Edit = new WorkspaceEdit() { DocumentChanges = null } }; var languageServer = CreateLanguageServer(resolvedCodeAction); CreateCodeActionResolver(out var codeActionParams, out var csharpCodeActionResolver, languageServer: languageServer); // Act var returnedCodeAction = await csharpCodeActionResolver.ResolveAsync(codeActionParams, DefaultUnresolvedCodeAction, default); // Assert Assert.Equal(DefaultUnresolvedCodeAction.Title, returnedCodeAction.Title); }
public async Task <RazorCodeAction> Handle(RazorCodeAction request, CancellationToken cancellationToken) { if (request is null) { throw new ArgumentNullException(nameof(request)); } if (!(request.Data is JObject paramsObj)) { Debug.Fail($"Invalid CodeAction Received '{request.Title}'."); return(request); } var resolutionParams = paramsObj.ToObject <RazorCodeActionResolutionParams>(); _logger.LogInformation($"Resolving workspace edit for action {GetCodeActionId(resolutionParams)}."); switch (resolutionParams.Language) { case LanguageServerConstants.CodeActions.Languages.Razor: return(await ResolveRazorCodeAction( request, resolutionParams, cancellationToken).ConfigureAwait(false)); case LanguageServerConstants.CodeActions.Languages.CSharp: return(await ResolveCSharpCodeAction( request, resolutionParams, cancellationToken)); default: Debug.Fail($"Invalid CodeAction.Data.Language. Received {GetCodeActionId(resolutionParams)}."); return(request); } }
public async Task ResolveRazorCodeAction_ResolveMultipleRazorProviders_SecondMatches() { // Arrange var codeActionEndpoint = new CodeActionResolutionEndpoint( new RazorCodeActionResolver[] { new MockRazorNullCodeActionResolver("A"), new MockRazorCodeActionResolver("B"), }, Array.Empty <CSharpCodeActionResolver>(), LoggerFactory); var codeAction = new RazorCodeAction(); var request = new RazorCodeActionResolutionParams() { Action = "B", Language = LanguageServerConstants.CodeActions.Languages.Razor, Data = new AddUsingsCodeActionParams() }; // Act var resolvedCodeAction = await codeActionEndpoint.ResolveRazorCodeActionAsync(codeAction, request, default); // Assert Assert.NotNull(resolvedCodeAction.Edit); }
public override Task <RazorCodeAction[]> ProvideAsync(RazorCodeActionContext context, CancellationToken cancellationToken) { if (context is null) { return(EmptyResult); } if (!FileKinds.IsComponent(context.CodeDocument.GetFileKind())) { return(EmptyResult); } var change = new SourceChange(context.Location.AbsoluteIndex, length: 0, newText: string.Empty); var syntaxTree = context.CodeDocument.GetSyntaxTree(); if (syntaxTree?.Root is null) { return(EmptyResult); } var owner = syntaxTree.Root.LocateOwner(change); if (owner == null) { Debug.Fail("Owner should never be null."); return(EmptyResult); } var node = owner.Ancestors().FirstOrDefault(n => n.Kind == SyntaxKind.RazorDirective); if (node == null || !(node is RazorDirectiveSyntax directiveNode)) { return(EmptyResult); } // Make sure we've found a @code or @functions if (directiveNode.DirectiveDescriptor != ComponentCodeDirective.Directive && directiveNode.DirectiveDescriptor != FunctionsDirective.Directive) { return(EmptyResult); } // No code action if malformed if (directiveNode.GetDiagnostics().Any(d => d.Severity == RazorDiagnosticSeverity.Error)) { return(EmptyResult); } var csharpCodeBlockNode = directiveNode.Body.DescendantNodes().FirstOrDefault(n => n is CSharpCodeBlockSyntax); if (csharpCodeBlockNode is null) { return(EmptyResult); } if (HasUnsupportedChildren(csharpCodeBlockNode)) { return(EmptyResult); } // Do not provide code action if the cursor is inside the code block if (context.Location.AbsoluteIndex > csharpCodeBlockNode.SpanStart) { return(EmptyResult); } var actionParams = new ExtractToCodeBehindCodeActionParams() { Uri = context.Request.TextDocument.Uri, ExtractStart = csharpCodeBlockNode.Span.Start, ExtractEnd = csharpCodeBlockNode.Span.End, RemoveStart = directiveNode.Span.Start, RemoveEnd = directiveNode.Span.End }; var resolutionParams = new RazorCodeActionResolutionParams() { Action = LanguageServerConstants.CodeActions.ExtractToCodeBehindAction, Data = actionParams, }; var codeAction = new RazorCodeAction() { Title = Title, Data = resolutionParams }; return(Task.FromResult(new[] { codeAction })); }
public abstract Task <RazorCodeAction> ResolveAsync( CSharpCodeActionParams csharpParams, RazorCodeAction codeAction, CancellationToken cancellationToken);
public async override Task <RazorCodeAction> ResolveAsync( CSharpCodeActionParams csharpParams, RazorCodeAction codeAction, CancellationToken cancellationToken) { if (csharpParams is null) { throw new ArgumentNullException(nameof(csharpParams)); } if (codeAction is null) { throw new ArgumentNullException(nameof(codeAction)); } var resolvedCodeAction = await ResolveCodeActionWithServerAsync(codeAction, cancellationToken); if (resolvedCodeAction.Edit?.DocumentChanges is null) { // Unable to resolve code action with server, return original code action return(codeAction); } if (resolvedCodeAction.Edit.DocumentChanges.Count() != 1) { // We don't yet support multi-document code actions, return original code action return(codeAction); } cancellationToken.ThrowIfCancellationRequested(); var documentSnapshot = await Task.Factory.StartNew(() => { _documentResolver.TryResolveDocument(csharpParams.RazorFileUri.GetAbsoluteOrUNCPath(), out var documentSnapshot); return(documentSnapshot); }, cancellationToken, TaskCreationOptions.None, _foregroundDispatcher.ForegroundScheduler).ConfigureAwait(false); if (documentSnapshot is null) { return(codeAction); } var documentChanged = resolvedCodeAction.Edit.DocumentChanges.First(); if (!documentChanged.IsTextDocumentEdit) { // Only Text Document Edit changes are supported currently, return original code action return(codeAction); } cancellationToken.ThrowIfCancellationRequested(); var csharpTextEdits = documentChanged.TextDocumentEdit.Edits.ToArray(); // Remaps the text edits from the generated C# to the razor file, // as well as applying appropriate formatting. var formattedEdits = await _razorFormattingService.ApplyFormattedEditsAsync( csharpParams.RazorFileUri, documentSnapshot, RazorLanguageKind.CSharp, csharpTextEdits, DefaultFormattingOptions, cancellationToken, bypassValidationPasses : true); cancellationToken.ThrowIfCancellationRequested(); var documentVersion = await Task.Factory.StartNew(() => { _documentVersionCache.TryGetDocumentVersion(documentSnapshot, out var version); return(version); }, cancellationToken, TaskCreationOptions.None, _foregroundDispatcher.ForegroundScheduler).ConfigureAwait(false); var codeDocumentIdentifier = new VersionedTextDocumentIdentifier() { Uri = csharpParams.RazorFileUri, Version = documentVersion.Value }; resolvedCodeAction.Edit = new WorkspaceEdit() { DocumentChanges = new[] { new WorkspaceEditDocumentChange( new TextDocumentEdit() { TextDocument = codeDocumentIdentifier, Edits = formattedEdits, } ) } }; return(resolvedCodeAction); }
public override Task <RazorCodeAction> ResolveAsync(CSharpCodeActionParams csharpParams, RazorCodeAction codeAction, CancellationToken cancellationToken) { return(Task.FromResult <RazorCodeAction>(null)); }
public override Task <RazorCodeAction> ResolveAsync(CSharpCodeActionParams csharpParams, RazorCodeAction codeAction, CancellationToken cancellationToken) { codeAction.Edit = new WorkspaceEdit(); return(Task.FromResult(codeAction)); }