public async Task Handle_ExistingComponent_SupportsFileCreationFalse_ReturnsResults() { // Arrange var documentPath = "c:/Test.razor"; var contents = "<Component></Component>"; var request = new CodeActionParams() { TextDocument = new TextDocumentIdentifier(new Uri(documentPath)), Range = new Range(new Position(0, 0), new Position(0, 0)), }; var location = new SourceLocation(1, -1, -1); var context = CreateRazorCodeActionContext(request, location, documentPath, contents, new SourceSpan(contents.IndexOf("Component", StringComparison.Ordinal), 9), supportsFileCreation: false); var provider = new ComponentAccessibilityCodeActionProvider(new DefaultTagHelperFactsService(), FilePathNormalizer); // Act var commandOrCodeActionContainer = await provider.ProvideAsync(context, default); // Assert Assert.Collection(commandOrCodeActionContainer, e => { Assert.Equal("@using Fully.Qualified", e.Title); Assert.NotNull(e.Data); Assert.Null(e.Edit); }, e => { Assert.Equal("Fully.Qualified.Component", e.Title); Assert.NotNull(e.Edit); Assert.NotNull(e.Edit.DocumentChanges); Assert.Null(e.Data); }); }
private static RazorCodeActionContext CreateRazorCodeActionContext(CodeActionParams request, SourceLocation location, string filePath, string text, SourceSpan componentSourceSpan, bool supportsFileCreation = true) { var shortComponent = TagHelperDescriptorBuilder.Create(ComponentMetadata.Component.TagHelperKind, "Fully.Qualified.Component", "TestAssembly"); shortComponent.TagMatchingRule(rule => rule.TagName = "Component"); var fullyQualifiedComponent = TagHelperDescriptorBuilder.Create(ComponentMetadata.Component.TagHelperKind, "Fully.Qualified.Component", "TestAssembly"); fullyQualifiedComponent.TagMatchingRule(rule => rule.TagName = "Fully.Qualified.Component"); var tagHelpers = new[] { shortComponent.Build(), fullyQualifiedComponent.Build() }; var sourceDocument = TestRazorSourceDocument.Create(text, filePath: filePath, relativePath: filePath); var projectEngine = RazorProjectEngine.Create(builder => builder.AddTagHelpers(tagHelpers)); var codeDocument = projectEngine.ProcessDesignTime(sourceDocument, FileKinds.Component, Array.Empty <RazorSourceDocument>(), tagHelpers); var cSharpDocument = codeDocument.GetCSharpDocument(); var diagnosticDescriptor = new RazorDiagnosticDescriptor("RZ10012", () => "", RazorDiagnosticSeverity.Error); var diagnostic = RazorDiagnostic.Create(diagnosticDescriptor, componentSourceSpan); var cSharpDocumentWithDiagnostic = RazorCSharpDocument.Create(cSharpDocument.GeneratedCode, cSharpDocument.Options, new[] { diagnostic }); codeDocument.SetCSharpDocument(cSharpDocumentWithDiagnostic); var documentSnapshot = Mock.Of <DocumentSnapshot>(document => document.GetGeneratedOutputAsync() == Task.FromResult(codeDocument) && document.GetTextAsync() == Task.FromResult(codeDocument.GetSourceText()) && document.Project.TagHelpers == tagHelpers, MockBehavior.Strict); var sourceText = SourceText.From(text); var context = new RazorCodeActionContext(request, documentSnapshot, codeDocument, location, sourceText, supportsFileCreation, supportsCodeActionResolve: true); return(context); }
public async Task ProvideAsync_FunctionsBlock_SingleLine_ValidCodeActions_ReturnsProvidedCodeAction() { // Arrange var documentPath = "c:/Test.razor"; var contents = "@functions { Path; }"; var request = new CodeActionParams() { TextDocument = new TextDocumentIdentifier(new Uri(documentPath)), Range = new Range(), Context = new CodeActionContext() }; var location = new SourceLocation(13, -1, -1); var context = CreateRazorCodeActionContext(request, location, documentPath, contents, new SourceSpan(13, 4)); context.CodeDocument.SetFileKind(FileKinds.Legacy); var provider = new DefaultCSharpCodeActionProvider(); // Act var providedCodeActions = await provider.ProvideAsync(context, _supportedCodeActions, default); // Assert Assert.Equal(_supportedCodeActions.Length, providedCodeActions.Count); var providedNames = providedCodeActions.Select(action => action.Name); var expectedNames = _supportedCodeActions.Select(action => action.Name); Assert.Equal(expectedNames, providedNames); }
public async Task Handle_NewComponent_SupportsFileCreationTrue_ReturnsResult() { // Arrange var documentPath = "c:/Test.razor"; var contents = "<NewComponent></NewComponent>"; var request = new CodeActionParams() { TextDocument = new TextDocumentIdentifier(new Uri(documentPath)), Range = new Range(new Position(0, 0), new Position(0, 0)), }; var location = new SourceLocation(1, -1, -1); var context = CreateRazorCodeActionContext(request, location, documentPath, contents, new SourceSpan(contents.IndexOf("Component", StringComparison.Ordinal), 9), supportsFileCreation: true); var provider = new ComponentAccessibilityCodeActionProvider(new DefaultTagHelperFactsService(), FilePathNormalizer); // Act var commandOrCodeActionContainer = await provider.ProvideAsync(context, default); // Assert var command = Assert.Single(commandOrCodeActionContainer); Assert.Equal("Create component from tag", command.Title); Assert.NotNull(command.Data); }
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); }
public async Task ProvideAsync_SupportsCodeActionResolveFalse_ValidCodeActions_ReturnsEmpty() { // 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), supportsCodeActionResolve: false); context.CodeDocument.SetFileKind(FileKinds.Legacy); var provider = new DefaultCSharpCodeActionProvider(); // Act var providedCodeActions = await provider.ProvideAsync(context, _supportedCodeActions, default); // Assert Assert.Empty(providedCodeActions); }
public async Task ProvideCodeActionsAsync_CannotLookupVirtualDocument_ReturnsNullAsync() { // Arrange var testDocUri = new Uri("C:/path/to/file.razor"); LSPDocumentSnapshot testDocument = new TestLSPDocumentSnapshot(testDocUri, 0); var documentManager = new Mock <TrackingLSPDocumentManager>(MockBehavior.Strict); documentManager.Setup(manager => manager.TryGetDocument(It.IsAny <Uri>(), out testDocument)) .Returns(true); var target = new DefaultRazorLanguageServerCustomMessageTarget(documentManager.Object); var request = new CodeActionParams() { TextDocument = new TextDocumentIdentifier() { Uri = new Uri("C:/path/to/file.razor") } }; // Act var result = await target.ProvideCodeActionsAsync(request, CancellationToken.None); // Assert Assert.Null(result); }
private static VSCodeAction GenerateVSCodeAction( CodeActionParams request, CodeAction codeAction, CodeActionKind codeActionKind, string currentTitle = "") { using var _ = ArrayBuilder <VSCodeAction> .GetInstance(out var nestedActions); if (!string.IsNullOrEmpty(currentTitle)) { // Adding a delimiter for nested code actions, e.g. 'Suppress or Configure issues|Suppress IDEXXXX|in Source' currentTitle += '|'; } currentTitle += codeAction.Title; // Nested code actions' unique identifiers consist of: parent code action unique identifier + '|' + title of code action foreach (var action in codeAction.NestedCodeActions) { nestedActions.Add(GenerateVSCodeAction(request, action, codeActionKind, currentTitle)); } return(new VSCodeAction { Title = codeAction.Title, Kind = codeActionKind, Diagnostics = request.Context.Diagnostics, Children = nestedActions.ToArray(), Data = new CodeActionResolveData(currentTitle, request.Range, request.TextDocument) }); }
private static VSInternalCodeAction GenerateVSCodeAction( CodeActionParams request, SourceText documentText, IUnifiedSuggestedAction suggestedAction, LSP.CodeActionKind codeActionKind, UnifiedSuggestedActionSetPriority setPriority, LSP.Range?applicableRange, int currentSetNumber, ref int currentHighestSetNumber, string currentTitle = "") { if (!string.IsNullOrEmpty(currentTitle)) { // Adding a delimiter for nested code actions, e.g. 'Suppress or Configure issues|Suppress IDEXXXX|in Source' currentTitle += '|'; } var codeAction = suggestedAction.OriginalCodeAction; currentTitle += codeAction.Title; // Nested code actions' unique identifiers consist of: parent code action unique identifier + '|' + title of code action var nestedActions = GenerateNestedVSCodeActions(request, documentText, suggestedAction, codeActionKind, ref currentHighestSetNumber, currentTitle); return(new VSInternalCodeAction { Title = codeAction.Title, Kind = codeActionKind, Diagnostics = request.Context.Diagnostics, Children = nestedActions, Priority = UnifiedSuggestedActionSetPriorityToPriorityLevel(setPriority), Group = $"Roslyn{currentSetNumber}", ApplicableRange = applicableRange, Data = new CodeActionResolveData(currentTitle, codeAction.CustomTags, request.Range, request.TextDocument) });
public override async Task <VSCodeAction[]> ProvideCodeActionsAsync(CodeActionParams codeActionParams, CancellationToken cancellationToken) { if (codeActionParams is null) { throw new ArgumentNullException(nameof(codeActionParams)); } if (!_documentManager.TryGetDocument(codeActionParams.TextDocument.Uri, out var documentSnapshot)) { return(null); } if (!documentSnapshot.TryGetVirtualDocument <CSharpVirtualDocumentSnapshot>(out var csharpDoc)) { return(null); } codeActionParams.TextDocument.Uri = csharpDoc.Uri; var results = await _requestInvoker.ReinvokeRequestOnMultipleServersAsync <CodeActionParams, VSCodeAction[]>( Methods.TextDocumentCodeActionName, LanguageServerKind.CSharp.ToContentType(), SupportsCSharpCodeActions, codeActionParams, cancellationToken).ConfigureAwait(false); return(results.SelectMany(l => l).ToArray()); }
static VSInternalCodeAction[] GenerateNestedVSCodeActions( CodeActionParams request, SourceText documentText, IUnifiedSuggestedAction suggestedAction, CodeActionKind codeActionKind, ref int currentHighestSetNumber, string currentTitle) { if (suggestedAction is not UnifiedSuggestedActionWithNestedActions suggestedActionWithNestedActions) { return(Array.Empty <VSInternalCodeAction>()); } using var _ = ArrayBuilder <VSInternalCodeAction> .GetInstance(out var nestedActions); foreach (var nestedActionSet in suggestedActionWithNestedActions.NestedActionSets) { // Nested code action sets should each have a unique set number that is not yet assigned to any set. var nestedSetNumber = ++currentHighestSetNumber; foreach (var nestedSuggestedAction in nestedActionSet.Actions) { nestedActions.Add(GenerateVSCodeAction( request, documentText, nestedSuggestedAction, codeActionKind, nestedActionSet.Priority, applicableRange: nestedActionSet.ApplicableToSpan.HasValue ? ProtocolConversions.TextSpanToRange(nestedActionSet.ApplicableToSpan.Value, documentText) : null, nestedSetNumber, ref currentHighestSetNumber, currentTitle)); } } return(nestedActions.ToArray()); }
public async Task Handle_MissingDiagnostics_ReturnsEmpty() { // Arrange var documentPath = "c:/Test.razor"; var contents = ""; var request = new CodeActionParams() { TextDocument = new TextDocumentIdentifier(new Uri(documentPath)), Range = new Range(), Context = new CodeActionContext() { Diagnostics = null } }; var location = new SourceLocation(0, -1, -1); var context = CreateRazorCodeActionContext(request, location, documentPath, contents, new SourceSpan(0, 0)); context.CodeDocument.SetFileKind(FileKinds.Legacy); var provider = new TypeAccessibilityCodeActionProvider(); var csharpCodeActions = new[] { new RazorCodeAction() { Title = "System.Net.Dns" } }; // Act var results = await provider.ProvideAsync(context, csharpCodeActions, default); // Assert Assert.Empty(results); }
public async Task Handle_InvalidCodeActions_ReturnsEmpty() { // Arrange var documentPath = "c:/Test.razor"; var contents = ""; var request = new CodeActionParams() { TextDocument = new TextDocumentIdentifier(new Uri(documentPath)), Range = new Range(), Context = new CodeActionContext() { Diagnostics = new Container <Diagnostic>( new Diagnostic() { Severity = DiagnosticSeverity.Error, Code = new DiagnosticCode("CS0246") } ) } }; var location = new SourceLocation(0, -1, -1); var context = CreateRazorCodeActionContext(request, location, documentPath, contents, new SourceSpan(0, 0)); context.CodeDocument.SetFileKind(FileKinds.Legacy); var provider = new TypeAccessibilityCodeActionProvider(); var csharpCodeActions = Array.Empty <RazorCodeAction>(); // Act var results = await provider.ProvideAsync(context, csharpCodeActions, default); // Assert Assert.Empty(results); }
public void NonStandardCharactersTest(string expected) { var model = new CodeActionParams() { Context = new CodeActionContext() { Diagnostics = new[] { new Diagnostic() { Code = new DiagnosticCode("abcd"), Message = "message", Range = new Range(new Position(1, 1), new Position(2, 2)), Severity = DiagnosticSeverity.Error, Source = "csharp" } } }, Range = new Range(new Position(1, 1), new Position(2, 2)), TextDocument = new TextDocumentIdentifier() { // ТаЉ - Chinese for tree Uri = new Uri("file:///test/123/%E6%A0%91.cs") } }; var result = Fixture.SerializeObject(model); result.Should().Be(expected); var deresult = new Serializer(ClientVersion.Lsp3).DeserializeObject <CodeActionParams>(expected); deresult.Should().BeEquivalentTo(model); }
public async Task Handle_OneCodeActionProviderWithMultipleCodeActions() { // Arrange var documentPath = "C:/path/to/Page.razor"; var codeDocument = CreateCodeDocument("@code {}"); var documentResolver = CreateDocumentResolver(documentPath, codeDocument); var codeActionEndpoint = new CodeActionEndpoint( _documentMappingService, new RazorCodeActionProvider[] { new MockMultipleRazorCodeActionProvider(), }, Array.Empty <CSharpCodeActionProvider>(), Dispatcher, documentResolver, _languageServer, _languageServerFeatureOptions) { _supportsCodeActionResolve = false }; var request = new CodeActionParams() { TextDocument = new TextDocumentIdentifier(new Uri(documentPath)), Range = new Range(new Position(0, 1), new Position(0, 1)), Context = new OmniSharpVSCodeActionContext() }; // Act var commandOrCodeActionContainer = await codeActionEndpoint.Handle(request, default); // Assert Assert.Equal(2, commandOrCodeActionContainer.Count()); }
public async Task Handle_InFunctionsDirective() { // Arrange var documentPath = "c:/Test.razor"; var contents = "@page \"/test\"\n@functions { private var x = 1; }"; var request = new CodeActionParams() { TextDocument = new TextDocumentIdentifier(new Uri(documentPath)), Range = new Range(), }; var location = new SourceLocation(contents.IndexOf("functions", StringComparison.Ordinal), -1, -1); var context = CreateRazorCodeActionContext(request, location, documentPath, contents); var provider = new ExtractToCodeBehindCodeActionProvider(); // Act var commandOrCodeActionContainer = await provider.ProvideAsync(context, default); // Assert var codeAction = Assert.Single(commandOrCodeActionContainer); var razorCodeActionResolutionParams = codeAction.Data as RazorCodeActionResolutionParams; var actionParams = razorCodeActionResolutionParams.Data as ExtractToCodeBehindCodeActionParams; Assert.Equal(14, actionParams.RemoveStart); Assert.Equal(24, actionParams.ExtractStart); Assert.Equal(47, actionParams.ExtractEnd); Assert.Equal(47, actionParams.RemoveEnd); }
public override Task <CommandOrCodeActionContainer> Handle(CodeActionParams request, CancellationToken cancellationToken) { var documentUri = request.TextDocument.Uri; var compilationContext = this.compilationManager.GetCompilation(documentUri); if (compilationContext == null) { return(Task.FromResult(new CommandOrCodeActionContainer())); } var requestStartOffset = PositionHelper.GetOffset(compilationContext.LineStarts, request.Range.Start); var requestEndOffset = request.Range.Start != request.Range.End ? PositionHelper.GetOffset(compilationContext.LineStarts, request.Range.End) : requestStartOffset; var compilation = compilationContext.Compilation; var semanticModel = compilation.GetEntrypointSemanticModel(); var diagnostics = semanticModel.GetAllDiagnostics(); var quickFixes = diagnostics .Where(fixable => fixable.Span.ContainsInclusive(requestStartOffset) || fixable.Span.ContainsInclusive(requestEndOffset) || (requestStartOffset <= fixable.Span.Position && fixable.GetEndPosition() <= requestEndOffset)) .OfType <IFixable>() .SelectMany(fixable => fixable.Fixes.Select(fix => CreateQuickFix(request.TextDocument.Uri, compilationContext, fix))); List <CommandOrCodeAction> commandOrCodeActions = new(); commandOrCodeActions.AddRange(quickFixes); var coreCompilerErrors = diagnostics .Where(diagnostic => !diagnostic.CanBeSuppressed()); var diagnosticsThatCanBeSuppressed = diagnostics .Where(diagnostic => (diagnostic.Span.ContainsInclusive(requestStartOffset) || diagnostic.Span.ContainsInclusive(requestEndOffset) || (requestStartOffset <= diagnostic.Span.Position && diagnostic.GetEndPosition() <= requestEndOffset))) .Except(coreCompilerErrors); HashSet <string> diagnosticCodesToSuppressInline = new(); foreach (IDiagnostic diagnostic in diagnosticsThatCanBeSuppressed) { if (!diagnosticCodesToSuppressInline.Contains(diagnostic.Code)) { diagnosticCodesToSuppressInline.Add(diagnostic.Code); var commandOrCodeAction = DisableDiagnostic(documentUri, diagnostic.Code, semanticModel.SourceFile, diagnostic.Span, compilationContext.LineStarts); if (commandOrCodeAction is not null) { commandOrCodeActions.Add(commandOrCodeAction); } } } return(Task.FromResult(new CommandOrCodeActionContainer(commandOrCodeActions))); }
internal async Task <RazorCodeActionContext> GenerateRazorCodeActionContextAsync(CodeActionParams request, CancellationToken cancellationToken) { var documentSnapshot = await _projectSnapshotManagerDispatcher.RunOnDispatcherThreadAsync(() => { _documentResolver.TryResolveDocument(request.TextDocument.Uri.GetAbsoluteOrUNCPath(), out var documentSnapshot); return(documentSnapshot); }, cancellationToken).ConfigureAwait(false); if (documentSnapshot is null) { return(null); } var codeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); if (codeDocument.IsUnsupported()) { return(null); } var sourceText = await documentSnapshot.GetTextAsync().ConfigureAwait(false); // VS Provides `CodeActionParams.Context.SelectionRange` in addition to // `CodeActionParams.Range`. The `SelectionRange` is relative to where the // code action was invoked (ex. line 14, char 3) whereas the `Range` is // always at the start of the line (ex. line 14, char 0). We want to utilize // the relative positioning to ensure we provide code actions for the appropriate // context. // // Note: VS Code doesn't provide a `SelectionRange`. var vsCodeActionContext = (OmniSharpVSCodeActionContext)request.Context; if (vsCodeActionContext.SelectionRange != null) { request = request with { Range = vsCodeActionContext.SelectionRange }; } var linePosition = new LinePosition( request.Range.Start.Line, request.Range.Start.Character); var hostDocumentIndex = sourceText.Lines.GetPosition(linePosition); var location = new SourceLocation( hostDocumentIndex, request.Range.Start.Line, request.Range.Start.Character); var context = new RazorCodeActionContext( request, documentSnapshot, codeDocument, location, sourceText, _languageServerFeatureOptions.SupportsFileManipulation, _supportsCodeActionResolve); return(context); }
public async Task ShouldRouteToCorrect_Request_WithManyHandlers() { var textDocumentSyncHandler = TextDocumentSyncHandlerExtensions.With(DocumentSelector.ForPattern("**/*.cs"), "csharp"); var textDocumentSyncHandler2 = TextDocumentSyncHandlerExtensions.With(DocumentSelector.ForPattern("**/*.cake"), "csharp"); textDocumentSyncHandler.Handle(Arg.Any <DidSaveTextDocumentParams>(), Arg.Any <CancellationToken>()).Returns(Unit.Value); textDocumentSyncHandler2.Handle(Arg.Any <DidSaveTextDocumentParams>(), Arg.Any <CancellationToken>()).Returns(Unit.Value); var codeActionHandler = Substitute.For <ICodeActionHandler>(); codeActionHandler.GetRegistrationOptions().Returns(new CodeActionRegistrationOptions() { DocumentSelector = DocumentSelector.ForPattern("**/*.cs") }); codeActionHandler .Handle(Arg.Any <CodeActionParams>(), Arg.Any <CancellationToken>()) .Returns(new CommandOrCodeActionContainer()); var registry = new TestLanguageServerRegistry(); var codeActionDelegate = Substitute.For <Func <CodeActionParams, CancellationToken, Task <CommandOrCodeActionContainer> > >(); codeActionDelegate.Invoke(Arg.Any <CodeActionParams>(), Arg.Any <CancellationToken>()) .Returns(new CommandOrCodeActionContainer()); registry.OnCodeAction( codeActionDelegate, new CodeActionRegistrationOptions() { DocumentSelector = DocumentSelector.ForPattern("**/*.cake") } ); var textDocumentIdentifiers = new TextDocumentIdentifiers(); AutoSubstitute.Provide(textDocumentIdentifiers); var handlerCollection = new HandlerCollection(SupportedCapabilitiesFixture.AlwaysTrue, textDocumentIdentifiers) { textDocumentSyncHandler, textDocumentSyncHandler2, codeActionHandler }; handlerCollection.Add(registry.Handlers); AutoSubstitute.Provide <IHandlerCollection>(handlerCollection); AutoSubstitute.Provide <IEnumerable <ILspHandlerDescriptor> >(handlerCollection); var mediator = AutoSubstitute.Resolve <LspRequestRouter>(); var id = Guid.NewGuid().ToString(); var @params = new CodeActionParams() { TextDocument = new TextDocumentIdentifier(new Uri("file:///c:/test/123.cake")) }; var request = new Request(id, DocumentNames.CodeAction, JObject.Parse(JsonConvert.SerializeObject(@params, new Serializer(ClientVersion.Lsp3).Settings))); await mediator.RouteRequest(mediator.GetDescriptor(request), request, CancellationToken.None); await codeActionHandler.Received(0).Handle(Arg.Any <CodeActionParams>(), Arg.Any <CancellationToken>()); await codeActionDelegate.Received(1).Invoke(Arg.Any <CodeActionParams>(), Arg.Any <CancellationToken>()); }
public async Task ProvideCodeActionsAsync_ReturnsCodeActionsAsync() { // Arrange var testDocUri = new Uri("C:/path/to/file.razor"); var testVirtualDocUri = new Uri("C:/path/to/file2.razor.g"); var testCSharpDocUri = new Uri("C:/path/to/file.razor.g.cs"); var testVirtualDocument = new TestVirtualDocumentSnapshot(testVirtualDocUri, 0); var csharpVirtualDocument = new CSharpVirtualDocumentSnapshot(testCSharpDocUri, Mock.Of <ITextSnapshot>(MockBehavior.Strict), 0); LSPDocumentSnapshot testDocument = new TestLSPDocumentSnapshot(testDocUri, 0, testVirtualDocument, csharpVirtualDocument); var documentManager = new Mock <TrackingLSPDocumentManager>(MockBehavior.Strict); documentManager.Setup(manager => manager.TryGetDocument(It.IsAny <Uri>(), out testDocument)) .Returns(true); var languageServer1Response = new[] { new VSCodeAction() { Title = "Response 1" } }; var languageServer2Response = new[] { new VSCodeAction() { Title = "Response 2" } }; IEnumerable <VSCodeAction[]> expectedResults = new List <VSCodeAction[]>() { languageServer1Response, languageServer2Response }; var requestInvoker = new Mock <LSPRequestInvoker>(MockBehavior.Strict); requestInvoker.Setup(invoker => invoker.ReinvokeRequestOnMultipleServersAsync <CodeActionParams, VSCodeAction[]>( Methods.TextDocumentCodeActionName, LanguageServerKind.CSharp.ToContentType(), It.IsAny <Func <JToken, bool> >(), It.IsAny <CodeActionParams>(), It.IsAny <CancellationToken>() )).Returns(Task.FromResult(expectedResults)); var uIContextManager = new Mock <RazorUIContextManager>(MockBehavior.Strict); var disposable = new Mock <IDisposable>(MockBehavior.Strict); var target = new DefaultRazorLanguageServerCustomMessageTarget(documentManager.Object, JoinableTaskContext, requestInvoker.Object, uIContextManager.Object, disposable.Object); var request = new CodeActionParams() { TextDocument = new LanguageServer.Protocol.TextDocumentIdentifier() { Uri = testDocUri } }; // Act var result = await target.ProvideCodeActionsAsync(request, CancellationToken.None); // Assert Assert.Collection(result, r => Assert.Equal(languageServer1Response[0].Title, r.Title), r => Assert.Equal(languageServer2Response[0].Title, r.Title)); }
/// <summary> /// Get, order, and filter code actions, and then transform them into VSCodeActions. /// </summary> /// <remarks> /// Used by CodeActionsHandler. /// </remarks> public static async Task <VSInternalCodeAction[]> GetVSCodeActionsAsync( CodeActionParams request, CodeActionsCache codeActionsCache, Document document, CodeActionOptions options, ICodeFixService codeFixService, ICodeRefactoringService codeRefactoringService, CancellationToken cancellationToken) { var actionSets = await GetActionSetsAsync( document, options, codeFixService, codeRefactoringService, request.Range, cancellationToken).ConfigureAwait(false); if (actionSets.IsDefaultOrEmpty) { return(Array.Empty <VSInternalCodeAction>()); } await codeActionsCache.UpdateActionSetsAsync(document, request.Range, actionSets, cancellationToken).ConfigureAwait(false); var documentText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); // Each suggested action set should have a unique set number, which is used for grouping code actions together. var currentHighestSetNumber = 0; using var _ = ArrayBuilder <VSInternalCodeAction> .GetInstance(out var codeActions); foreach (var set in actionSets) { var currentSetNumber = ++currentHighestSetNumber; foreach (var suggestedAction in set.Actions) { // Filter out code actions with options since they'll show dialogs and we can't remote the UI and the options. if (suggestedAction.OriginalCodeAction is CodeActionWithOptions) { continue; } // TO-DO: Re-enable code actions involving package manager once supported by LSP. // https://github.com/dotnet/roslyn/issues/48698 if (suggestedAction.OriginalCodeAction.Tags.Equals(WellKnownTagArrays.NuGet)) { continue; } codeActions.Add(GenerateVSCodeAction( request, documentText, suggestedAction: suggestedAction, codeActionKind: GetCodeActionKindFromSuggestedActionCategoryName(set.CategoryName !), setPriority: set.Priority, applicableRange: set.ApplicableToSpan.HasValue ? ProtocolConversions.TextSpanToRange(set.ApplicableToSpan.Value, documentText) : null, currentSetNumber: currentSetNumber, currentHighestSetNumber: ref currentHighestSetNumber)); } } return(codeActions.ToArray()); }
public override async Task <CommandOrCodeActionContainer> Handle(CodeActionParams request, CancellationToken cancellationToken) { var omnisharpRequest = new GetCodeActionsRequest { FileName = Helpers.FromUri(request.TextDocument.Uri), Column = request.Range.Start.Character, Line = request.Range.Start.Line, Selection = Helpers.FromRange(request.Range), }; var omnisharpResponse = await _getActionsHandler.Handle(omnisharpRequest); var codeActions = new List <CodeAction>(); foreach (var ca in omnisharpResponse.CodeActions) { CodeActionKind kind; if (ca.Identifier.StartsWith("using ")) { kind = CodeActionKind.QuickFix; } else if (ca.Identifier.StartsWith("Inline ")) { kind = CodeActionKind.RefactorInline; } else if (ca.Identifier.StartsWith("Extract ")) { kind = CodeActionKind.RefactorExtract; } else if (ca.Identifier.StartsWith("Change ")) { kind = CodeActionKind.QuickFix; } else { kind = CodeActionKind.Refactor; } codeActions.Add( new CodeAction { Title = ca.Name, Kind = kind, Diagnostics = new Container <Diagnostic>(), Edit = new WorkspaceEdit(), Command = Command.Create("omnisharp/executeCodeAction") .WithArguments(new CommandData() { Uri = request.TextDocument.Uri, Identifier = ca.Identifier, Name = ca.Name, Range = request.Range, }) with { Title = ca.Name } });
public async Task Handle_ValidCodeAction_VS_ReturnsCodeActions() { // 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() { Diagnostics = new Container <Diagnostic>() } }; var location = new SourceLocation(0, -1, -1); var context = CreateRazorCodeActionContext(request, location, documentPath, contents, new SourceSpan(8, 4), supportsCodeActionResolve: true); context.CodeDocument.SetFileKind(FileKinds.Legacy); var provider = new TypeAccessibilityCodeActionProvider(); var csharpCodeActions = new[] { new RazorCodeAction() { Title = "System.IO.Path", Name = "FullyQualify" }, new RazorCodeAction() { Title = "using System.IO;", Name = "AddImport" } }; // Act var results = await provider.ProvideAsync(context, csharpCodeActions, default); // Assert Assert.Collection(results, r => { Assert.Equal("@using System.IO", r.Title); Assert.Null(r.Edit); Assert.NotNull(r.Data); var resolutionParams = (r.Data as JObject).ToObject <RazorCodeActionResolutionParams>(); Assert.Equal(LanguageServerConstants.CodeActions.AddUsing, resolutionParams.Action); }, r => { Assert.Equal("System.IO.Path", r.Title); Assert.Null(r.Edit); Assert.NotNull(r.Data); } ); }
public async Task GetCSharpCodeActionsFromLanguageServerAsync_ReturnsCodeActions() { // Arrange var documentPath = "C:/path/to/Page.razor"; var codeDocument = CreateCodeDocument("@code {}"); var documentResolver = CreateDocumentResolver(documentPath, codeDocument); var projectedRange = new Range(new Position(15, 2), new Position(15, 2)); var documentMappingService = CreateDocumentMappingService(projectedRange); var languageServer = CreateLanguageServer(); var codeActionEndpoint = new CodeActionEndpoint( documentMappingService, Array.Empty <RazorCodeActionProvider>(), new CSharpCodeActionProvider[] { new MockCSharpCodeActionProvider() }, Dispatcher, documentResolver, languageServer, _languageServerFeatureOptions) { _supportsCodeActionResolve = false }; var initialRange = new Range(new Position(0, 1), new Position(0, 1)); var request = new CodeActionParams() { TextDocument = new TextDocumentIdentifier(new Uri(documentPath)), Range = initialRange, Context = new OmniSharpVSCodeActionContext() { SelectionRange = initialRange } }; var context = await codeActionEndpoint.GenerateRazorCodeActionContextAsync(request, default); // Act var results = await codeActionEndpoint.GetCSharpCodeActionsFromLanguageServerAsync(context, default); // Assert Assert.Single(results); var diagnostics = results.Single().Diagnostics.ToArray(); Assert.Equal(2, diagnostics.Length); // Diagnostic ranges contain the projected range for // 1. Range // 2. SelectionRange // // This helps verify that the CodeActionEndpoint is mapping the // ranges correctly using the mapping service Assert.Equal(projectedRange, diagnostics[0].Range); Assert.Equal(projectedRange, diagnostics[1].Range); }
public async Task Handle_InvalidDiagnostics_ReturnsEmpty() { // Arrange var documentPath = "c:/Test.razor"; var contents = ""; var request = new CodeActionParams() { TextDocument = new TextDocumentIdentifier(new Uri(documentPath)), Range = new Range(), Context = new CodeActionContext() { Diagnostics = new Container <Diagnostic>( new Diagnostic() { // Invalid as Error is expected Severity = DiagnosticSeverity.Warning, Code = new DiagnosticCode("CS0534") }, new Diagnostic() { // Invalid as CS error code is expected Severity = DiagnosticSeverity.Error, Code = new DiagnosticCode(0246) }, new Diagnostic() { // Invalid as CS0534 or CS0535 is expected Severity = DiagnosticSeverity.Error, Code = new DiagnosticCode("CS0183") } ) } }; var location = new SourceLocation(0, -1, -1); var context = CreateRazorCodeActionContext(request, location, documentPath, contents, new SourceSpan(0, 0)); context.CodeDocument.SetFileKind(FileKinds.Legacy); var provider = new ImplementInterfaceAbstractClassCodeActionProvider(); var csharpCodeActions = new[] { new RazorCodeAction() { Title = "Implement abstract class" } }; // Act var results = await provider.ProvideAsync(context, csharpCodeActions, default); // Assert Assert.Empty(results); }
public async Task Handle_InvalidDiagnostics_VSCode_ReturnsEmpty() { // Arrange var documentPath = "c:/Test.razor"; var contents = ""; var request = new CodeActionParams() { TextDocument = new TextDocumentIdentifier(new Uri(documentPath)), Range = new Range(), Context = new CodeActionContext() { Diagnostics = new Container <Diagnostic>( new Diagnostic() { // Invalid as Error is expected Severity = DiagnosticSeverity.Warning, Code = new DiagnosticCode("CS0246") }, new Diagnostic() { // Invalid as CS error code is expected Severity = DiagnosticSeverity.Error, Code = new DiagnosticCode(0246) }, new Diagnostic() { // Invalid as CS0246 or CS0103 is expected Severity = DiagnosticSeverity.Error, Code = new DiagnosticCode("CS0183") } ) } }; var location = new SourceLocation(0, -1, -1); var context = CreateRazorCodeActionContext(request, location, documentPath, contents, new SourceSpan(0, 0), supportsCodeActionResolve: false); context.CodeDocument.SetFileKind(FileKinds.Legacy); var provider = new TypeAccessibilityCodeActionProvider(); var csharpCodeActions = new[] { new RazorCodeAction() { Title = "System.Net.Dns" } }; // Act var results = await provider.ProvideAsync(context, csharpCodeActions, default); // Assert Assert.Empty(results); }
public async Task RequestsCancellation() { var textDocumentSyncHandler = TextDocumentSyncHandlerExtensions.With(DocumentSelector.ForPattern("**/*.cs"), "csharp"); textDocumentSyncHandler.Handle(Arg.Any <DidSaveTextDocumentParams>(), Arg.Any <CancellationToken>()).Returns(Unit.Value); var codeActionHandler = Substitute.For <ICodeActionHandler>(); codeActionHandler.GetRegistrationOptions().Returns(new CodeActionRegistrationOptions() { DocumentSelector = DocumentSelector.ForPattern("**/*.cs") }); codeActionHandler .Handle(Arg.Any <CodeActionParams>(), Arg.Any <CancellationToken>()) .Returns(async(c) => { await Task.Delay(1000, c.Arg <CancellationToken>()); throw new XunitException("Task was not cancelled in time!"); return(new CommandOrCodeActionContainer()); }); var collection = new HandlerCollection(SupportedCapabilitiesFixture.AlwaysTrue, new TextDocumentIdentifiers()) { textDocumentSyncHandler, codeActionHandler }; AutoSubstitute.Provide <IHandlerCollection>(collection); AutoSubstitute.Provide <IEnumerable <ILspHandlerDescriptor> >(collection); var mediator = AutoSubstitute.Resolve <LspRequestRouter>(); var id = Guid.NewGuid().ToString(); var @params = new CodeActionParams() { TextDocument = new TextDocumentIdentifier(new Uri("file:///c:/test/123.cs")), Range = new Range(new Position(1, 1), new Position(2, 2)), Context = new CodeActionContext() { Diagnostics = new Container <Diagnostic>() } }; var request = new Request(id, "textDocument/codeAction", JObject.Parse(JsonConvert.SerializeObject(@params, new Serializer(ClientVersion.Lsp3).Settings))); var response = ((IRequestRouter <ILspHandlerDescriptor>)mediator).RouteRequest(request, CancellationToken.None); mediator.CancelRequest(id); var result = await response; result.IsError.Should().BeTrue(); result.Error.Should().BeEquivalentTo(new RequestCancelled()); }
public async Task ShouldRouteToCorrect_Request_WithManyHandlers() { var textDocumentSyncHandler = TextDocumentSyncHandlerExtensions.With(DocumentSelector.ForPattern("**/*.cs")); var textDocumentSyncHandler2 = TextDocumentSyncHandlerExtensions.With(DocumentSelector.ForPattern("**/*.cake")); textDocumentSyncHandler.Handle(Arg.Any <DidSaveTextDocumentParams>()).Returns(Task.CompletedTask); textDocumentSyncHandler2.Handle(Arg.Any <DidSaveTextDocumentParams>()).Returns(Task.CompletedTask); var codeActionHandler = Substitute.For <ICodeActionHandler>(); codeActionHandler.GetRegistrationOptions().Returns(new TextDocumentRegistrationOptions() { DocumentSelector = DocumentSelector.ForPattern("**/*.cs") }); codeActionHandler .Handle(Arg.Any <CodeActionParams>(), Arg.Any <CancellationToken>()) .Returns(new CommandContainer()); var codeActionHandler2 = Substitute.For <ICodeActionHandler>(); codeActionHandler2.GetRegistrationOptions().Returns(new TextDocumentRegistrationOptions() { DocumentSelector = DocumentSelector.ForPattern("**/*.cake") }); codeActionHandler2 .Handle(Arg.Any <CodeActionParams>(), Arg.Any <CancellationToken>()) .Returns(new CommandContainer()); var collection = new HandlerCollection { textDocumentSyncHandler, textDocumentSyncHandler2, codeActionHandler, codeActionHandler2 }; var handlerMatcherCollection = new HandlerMatcherCollection { new TextDocumentMatcher(_testLoggerFactory.CreateLogger <TextDocumentMatcher>(), collection.TextDocumentSyncHandlers) }; var mediator = new LspRequestRouter(collection, _testLoggerFactory, handlerMatcherCollection, new Serializer()); var id = Guid.NewGuid().ToString(); var @params = new CodeActionParams() { TextDocument = new TextDocumentIdentifier(new Uri("file:///c:/test/123.cake")) }; var request = new Request(id, DocumentNames.CodeAction, JObject.Parse(JsonConvert.SerializeObject(@params, new Serializer(ClientVersion.Lsp3).Settings))); await mediator.RouteRequest(mediator.GetDescriptor(request), request); await codeActionHandler.Received(0).Handle(Arg.Any <CodeActionParams>(), Arg.Any <CancellationToken>()); await codeActionHandler2.Received(1).Handle(Arg.Any <CodeActionParams>(), Arg.Any <CancellationToken>()); }
public RazorCodeActionContext( CodeActionParams request, DocumentSnapshot documentSnapshot, RazorCodeDocument codeDocument, SourceLocation location, SourceText sourceText, bool supportsFileCreation) { Request = request ?? throw new ArgumentNullException(nameof(request)); DocumentSnapshot = documentSnapshot ?? throw new ArgumentNullException(nameof(documentSnapshot)); CodeDocument = codeDocument ?? throw new ArgumentNullException(nameof(codeDocument)); Location = location; SourceText = sourceText ?? throw new ArgumentNullException(nameof(sourceText)); SupportsFileCreation = supportsFileCreation; }
public async Task Handle_NoDocument() { // Arrange var documentPath = "C:/path/to/Page.razor"; var codeActionEndpoint = new CodeActionEndpoint(new RazorCodeActionProvider[] { }, Dispatcher, EmptyDocumentResolver, LoggerFactory); var request = new CodeActionParams() { TextDocument = new TextDocumentIdentifier(new Uri(documentPath)), Range = new Range(new Position(0, 1), new Position(0, 1)), }; // Act var commandOrCodeActionContainer = await codeActionEndpoint.Handle(request, default); // Assert Assert.Null(commandOrCodeActionContainer); }