private static RazorCodeActionContext CreateRazorCodeActionContext(RazorCodeActionParams 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); var sourceText = SourceText.From(text); var context = new RazorCodeActionContext(request, documentSnapshot, codeDocument, location, sourceText, supportsFileCreation, supportsCodeActionResolve: true); return(context); }
public async Task Handle_InFunctionsDirective_SupportsFileCreationTrue_ReturnsResult() { // Arrange var documentPath = "c:/Test.razor"; var contents = "@page \"/test\"\n@functions { private var x = 1; }"; var request = new RazorCodeActionParams() { 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 async Task Handle_UnsupportedDocument() { // Arrange var documentPath = "C:/path/to/Page.razor"; var codeDocument = CreateCodeDocument("@code {}"); var documentResolver = CreateDocumentResolver(documentPath, codeDocument); codeDocument.SetUnsupported(); var codeActionEndpoint = new CodeActionEndpoint( DocumentMappingService, Array.Empty <RazorCodeActionProvider>(), Array.Empty <CSharpCodeActionProvider>(), Dispatcher, documentResolver, LanguageServer, LanguageServerFeatureOptions) { _supportsCodeActionResolve = false }; var request = new RazorCodeActionParams() { TextDocument = new TextDocumentIdentifier(new Uri(documentPath)), Range = new Range(new Position(0, 1), new Position(0, 1)), Context = new ExtendedCodeActionContext() }; // Act var commandOrCodeActionContainer = await codeActionEndpoint.Handle(request, default); // Assert Assert.Null(commandOrCodeActionContainer); }
public async Task Handle_ExistingComponent_SupportsFileCreationFalse_ReturnsResults() { // Arrange var documentPath = "c:/Test.razor"; var contents = "<Component></Component>"; var request = new RazorCodeActionParams() { 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); }); }
public async Task Handle_NewComponent_SupportsFileCreationTrue_ReturnsResult() { // Arrange var documentPath = "c:/Test.razor"; var contents = "<NewComponent></NewComponent>"; var request = new RazorCodeActionParams() { 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); }
private static RazorCodeActionContext CreateRazorCodeActionContext(RazorCodeActionParams request, SourceLocation location, string filePath, string text, bool supportsFileCreation = true) { var codeDocument = TestRazorCodeDocument.CreateEmpty(); codeDocument.SetFileKind(FileKinds.Component); var sourceDocument = TestRazorSourceDocument.Create(text, filePath: filePath, relativePath: filePath); var options = RazorParserOptions.Create(o => { o.Directives.Add(ComponentCodeDirective.Directive); o.Directives.Add(FunctionsDirective.Directive); }); var syntaxTree = RazorSyntaxTree.Parse(sourceDocument, options); codeDocument.SetSyntaxTree(syntaxTree); var documentSnapshot = Mock.Of <DocumentSnapshot>(document => document.GetGeneratedOutputAsync() == Task.FromResult(codeDocument) && document.GetTextAsync() == Task.FromResult(codeDocument.GetSourceText())); var sourceText = SourceText.From(text); var context = new RazorCodeActionContext(request, documentSnapshot, codeDocument, location, sourceText, supportsFileCreation); return(context); }
public async Task Handle_MultipleMixedProvider_SupportsCodeActionResolveTrue() { // 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 MockRazorCodeActionProvider(), new MockRazorCommandProvider(), new MockNullRazorCodeActionProvider() }, Array.Empty <CSharpCodeActionProvider>(), Dispatcher, documentResolver, LanguageServer, LanguageServerFeatureOptions) { _supportsCodeActionResolve = true }; var request = new RazorCodeActionParams() { TextDocument = new TextDocumentIdentifier(new Uri(documentPath)), Range = new Range(new Position(0, 1), new Position(0, 1)), Context = new ExtendedCodeActionContext() }; // Act var commandOrCodeActionContainer = await codeActionEndpoint.Handle(request, default); // Assert Assert.Collection(commandOrCodeActionContainer, c => { Assert.True(c.IsCodeAction); Assert.True(c.CodeAction is CodeAction); }, c => { Assert.True(c.IsCodeAction); Assert.True(c.CodeAction is CodeAction); }); }
public async Task <CommandOrCodeActionContainer> Handle(RazorCodeActionParams request, CancellationToken cancellationToken) { if (request is null) { throw new ArgumentNullException(nameof(request)); } var razorCodeActionContext = await GenerateRazorCodeActionContextAsync(request, cancellationToken).ConfigureAwait(false); if (razorCodeActionContext is null) { return(null); } cancellationToken.ThrowIfCancellationRequested(); var razorCodeActions = await GetRazorCodeActionsAsync(razorCodeActionContext, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); var csharpCodeActions = await GetCSharpCodeActionsAsync(razorCodeActionContext, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); var codeActions = Enumerable.Concat( razorCodeActions ?? Array.Empty <RazorCodeAction>(), csharpCodeActions ?? Array.Empty <RazorCodeAction>()); if (!codeActions.Any()) { return(null); } // We must cast the RazorCodeAction into a platform compliant code action // For VS (SupportsCodeActionResolve = true) this means just encapsulating the RazorCodeAction in the `CommandOrCodeAction` struct // For VS Code (SupportsCodeActionResolve = false) we must convert it into a CodeAction or Command before encapsulating in the `CommandOrCodeAction` struct. var commandsOrCodeActions = codeActions.Select(c => _supportsCodeActionResolve ? new CommandOrCodeAction(c) : c.AsVSCodeCommandOrCodeAction()); return(new CommandOrCodeActionContainer(commandsOrCodeActions)); }
public async Task GetCSharpCodeActionsFromLanguageServerAsync_InvalidRangeMapping() { // Arrange var documentPath = "C:/path/to/Page.razor"; var codeDocument = CreateCodeDocument("@code {}"); var documentResolver = CreateDocumentResolver(documentPath, codeDocument); Range projectedRange = null; var documentMappingService = Mock.Of <DefaultRazorDocumentMappingService>( d => d.TryMapToProjectedDocumentRange(It.IsAny <RazorCodeDocument>(), It.IsAny <Range>(), out projectedRange) == false ); 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 RazorCodeActionParams() { TextDocument = new TextDocumentIdentifier(new Uri(documentPath)), Range = initialRange, Context = new ExtendedCodeActionContext() }; var context = await codeActionEndpoint.GenerateRazorCodeActionContextAsync(request, default); // Act var results = await codeActionEndpoint.GetCSharpCodeActionsFromLanguageServerAsync(context, default); // Assert Assert.Empty(results); Assert.Equal(initialRange, context.Request.Range); }
public async Task Handle_MultipleMixedProvider() { // Arrange var documentPath = "C:/path/to/Page.razor"; var codeDocument = CreateCodeDocument("@code {}"); var documentResolver = CreateDocumentResolver(documentPath, codeDocument); var documentMappingService = CreateDocumentMappingService(); var languageServer = CreateLanguageServer(); var codeActionEndpoint = new CodeActionEndpoint( documentMappingService, new RazorCodeActionProvider[] { new MockRazorCodeActionProvider(), new MockNullRazorCodeActionProvider(), new MockRazorCodeActionProvider(), new MockNullRazorCodeActionProvider(), }, new CSharpCodeActionProvider[] { new MockCSharpCodeActionProvider(), new MockCSharpCodeActionProvider() }, Dispatcher, documentResolver, languageServer, LanguageServerFeatureOptions) { _supportsCodeActionResolve = false }; var request = new RazorCodeActionParams() { TextDocument = new TextDocumentIdentifier(new Uri(documentPath)), Range = new Range(new Position(0, 1), new Position(0, 1)), Context = new ExtendedCodeActionContext() }; // Act var commandOrCodeActionContainer = await codeActionEndpoint.Handle(request, default); // Assert Assert.Equal(4, commandOrCodeActionContainer.Count()); }
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 RazorCodeActionParams() { TextDocument = new TextDocumentIdentifier(new Uri(documentPath)), Range = initialRange, Context = new ExtendedCodeActionContext() }; var context = await codeActionEndpoint.GenerateRazorCodeActionContextAsync(request, default); // Act var results = await codeActionEndpoint.GetCSharpCodeActionsFromLanguageServerAsync(context, default); // Assert Assert.Single(results); Assert.Equal(projectedRange, context.Request.Range); }
public async Task Handle_InCodeDirectiveMalformed_ReturnsNull() { // Arrange var documentPath = "c:/Test.razor"; var contents = "@page \"/test\"\n@code"; var request = new RazorCodeActionParams() { TextDocument = new TextDocumentIdentifier(new Uri(documentPath)), Range = new Range(), }; var location = new SourceLocation(contents.IndexOf("code", StringComparison.Ordinal), -1, -1); var context = CreateRazorCodeActionContext(request, location, documentPath, contents); var provider = new ExtractToCodeBehindCodeActionProvider(); // Act var commandOrCodeActionContainer = await provider.ProvideAsync(context, default); // Assert Assert.Null(commandOrCodeActionContainer); }
public async Task Handle_CursorOutsideComponent() { // Arrange var documentPath = "c:/Test.razor"; var contents = " <Component></Component>"; var request = new RazorCodeActionParams() { TextDocument = new TextDocumentIdentifier(new Uri(documentPath)), Range = new Range(new Position(0, 0), new Position(0, 0)), }; var location = new SourceLocation(0, -1, -1); var context = CreateRazorCodeActionContext(request, location, documentPath, contents, new SourceSpan(contents.IndexOf("Component", StringComparison.Ordinal), 9)); var provider = new ComponentAccessibilityCodeActionProvider(new DefaultTagHelperFactsService(), FilePathNormalizer); // Act var commandOrCodeActionContainer = await provider.ProvideAsync(context, default); // Assert Assert.Null(commandOrCodeActionContainer); }
public async Task Handle_NoTagName_DoesNotProvideLightBulb() { // Arrange var documentPath = "c:/Test.razor"; var contents = "<"; var request = new RazorCodeActionParams() { TextDocument = new TextDocumentIdentifier(new Uri(documentPath)), Range = new Range(new Position(0, 1), new Position(0, 1)), }; var location = new SourceLocation(1, -1, -1); var context = CreateRazorCodeActionContext(request, location, documentPath, contents, new SourceSpan(0, 1)); var provider = new ComponentAccessibilityCodeActionProvider(new DefaultTagHelperFactsService(), FilePathNormalizer); // Act var commandOrCodeActionContainer = await provider.ProvideAsync(context, default); // Assert Assert.Null(commandOrCodeActionContainer); }
public async Task GenerateRazorCodeActionContextAsync_WithSelectionRange() { // 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 MockRazorCodeActionProvider() }, Array.Empty <CSharpCodeActionProvider>(), Dispatcher, documentResolver, LanguageServer, LanguageServerFeatureOptions) { _supportsCodeActionResolve = false }; var initialRange = new Range(new Position(0, 1), new Position(0, 1)); var selectionRange = new Range(new Position(0, 5), new Position(0, 5)); var request = new RazorCodeActionParams() { TextDocument = new TextDocumentIdentifier(new Uri(documentPath)), Range = initialRange, Context = new ExtendedCodeActionContext() { SelectionRange = selectionRange, } }; // Act var razorCodeActionContext = await codeActionEndpoint.GenerateRazorCodeActionContextAsync(request, default); // Assert Assert.NotNull(razorCodeActionContext); Assert.Equal(selectionRange, razorCodeActionContext.Request.Range); }
public async Task Handle_InvalidSyntaxTree_NoStartNode() { // Arrange var documentPath = "c:/Test.razor"; var contents = ""; var request = new RazorCodeActionParams() { TextDocument = new TextDocumentIdentifier(new Uri(documentPath)), Range = new Range(), }; 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 ComponentAccessibilityCodeActionProvider(new DefaultTagHelperFactsService(), FilePathNormalizer); // Act var commandOrCodeActionContainer = await provider.ProvideAsync(context, default); // Assert Assert.Null(commandOrCodeActionContainer); }
// internal for testing internal async Task <RazorCodeActionContext> GenerateRazorCodeActionContextAsync(RazorCodeActionParams request, CancellationToken cancellationToken) { var documentSnapshot = await Task.Factory.StartNew(() => { _documentResolver.TryResolveDocument(request.TextDocument.Uri.GetAbsoluteOrUNCPath(), out var documentSnapshot); return(documentSnapshot); }, cancellationToken, TaskCreationOptions.None, _foregroundDispatcher.ForegroundScheduler).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`. if (request.Context.SelectionRange != null) { request.Range = request.Context.SelectionRange; } // We hide `CodeActionParams.CodeActionContext` in order to capture // `RazorCodeActionParams.ExtendedCodeActionContext`, we must // restore this context to access diagnostics. (request as CodeActionParams).Context = request.Context; 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); }