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
        }
예제 #3
0
        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);
        }
예제 #4
0
        // 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);
        }
예제 #8
0
        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);
        }
예제 #9
0
        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);
        }
예제 #11
0
        // 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);
        }
예제 #12
0
        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);
        }
예제 #15
0
        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);
        }
예제 #16
0
        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);
        }
예제 #18
0
        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 }));
        }
예제 #21
0
 public abstract Task <RazorCodeAction> ResolveAsync(
     CSharpCodeActionParams csharpParams,
     RazorCodeAction codeAction,
     CancellationToken cancellationToken);
예제 #22
0
        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));
 }