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);
        }
示例#7
0
        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));
        }
示例#9
0
        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);
        }
示例#10
0
        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());
        }
示例#11
0
        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);
        }
示例#13
0
        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);
        }
示例#15
0
        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);
        }
示例#16
0
        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);
        }