Ejemplo n.º 1
0
        public async Task <CommandOrCodeActionContainer> Handle(CodeActionParams request, CancellationToken cancellationToken)
        {
            if (request is null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            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);

            var linePosition      = new LinePosition((int)request.Range.Start.Line, (int)request.Range.Start.Character);
            var hostDocumentIndex = sourceText.Lines.GetPosition(linePosition);
            var location          = new SourceLocation(hostDocumentIndex, (int)request.Range.Start.Line, (int)request.Range.Start.Character);

            var context = new RazorCodeActionContext(request, documentSnapshot, codeDocument, location);
            var tasks   = new List <Task <CommandOrCodeActionContainer> >();

            foreach (var provider in _providers)
            {
                var result = provider.ProvideAsync(context, cancellationToken);
                if (result != null)
                {
                    tasks.Add(result);
                }
            }

            var results = await Task.WhenAll(tasks).ConfigureAwait(false);

            var container = new List <CommandOrCodeAction>();

            foreach (var result in results)
            {
                if (result != null)
                {
                    foreach (var commandOrCodeAction in result)
                    {
                        container.Add(commandOrCodeAction);
                    }
                }
            }

            return(new CommandOrCodeActionContainer(container));
        }
        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);

            var sourceText = SourceText.From(text);

            var context = new RazorCodeActionContext(request, documentSnapshot, codeDocument, location, sourceText, supportsFileCreation, supportsCodeActionResolve: true);

            return(context);
        }
Ejemplo n.º 3
0
        protected static bool InFunctionsBlock(RazorCodeActionContext context)
        {
            var change     = new SourceChange(context.Location.AbsoluteIndex, length: 0, newText: string.Empty);
            var syntaxTree = context.CodeDocument.GetSyntaxTree();

            if (syntaxTree?.Root is null)
            {
                return(false);
            }

            var owner = syntaxTree.Root.LocateOwner(change);

            if (owner == null)
            {
                Debug.Fail("Owner should never be null.");
                return(false);
            }

            var node = owner.Ancestors().FirstOrDefault(n => n.Kind == SyntaxKind.RazorDirective);

            if (node == null || !(node is RazorDirectiveSyntax directiveNode))
            {
                return(false);
            }

            return(directiveNode.DirectiveDescriptor == FunctionsDirective.Directive);
        }
Ejemplo n.º 4
0
        private static RazorCodeActionContext CreateRazorCodeActionContext(
            CodeActionParams request,
            SourceLocation location,
            string filePath,
            string text,
            SourceSpan componentSourceSpan,
            bool supportsFileCreation      = true,
            bool supportsCodeActionResolve = true)
        {
            var tagHelpers     = Array.Empty <TagHelperDescriptor>();
            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);

            return(context);
        }
        public override Task <IReadOnlyList <RazorCodeAction> > ProvideAsync(RazorCodeActionContext context, CancellationToken cancellationToken)
        {
            var codeActions = new List <RazorCodeAction>();

            // Locate cursor
            var change = new SourceChange(context.Location.AbsoluteIndex, length: 0, newText: string.Empty);
            var node   = context.CodeDocument.GetSyntaxTree().Root.LocateOwner(change);

            if (node is null)
            {
                return(EmptyResult);
            }

            // Find start tag
            var startTag = (MarkupStartTagSyntax)node.Ancestors().FirstOrDefault(n => n is MarkupStartTagSyntax);

            if (startTag is null)
            {
                return(EmptyResult);
            }

            // Ignore if start tag has dots, as we only handle short tags
            if (startTag.Name.Content.Contains("."))
            {
                return(EmptyResult);
            }

            if (IsTagUnknown(startTag, context))
            {
                AddComponentAccessFromTag(context, startTag, codeActions);
                AddCreateComponentFromTag(context, startTag, codeActions);
            }

            return(Task.FromResult(codeActions as IReadOnlyList <RazorCodeAction>));
        }
        // 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);
        }
Ejemplo n.º 7
0
        public override Task <IReadOnlyList <RazorCodeAction> > ProvideAsync(
            RazorCodeActionContext context,
            IEnumerable <RazorCodeAction> codeActions,
            CancellationToken cancellationToken)
        {
            if (context is null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (codeActions is null)
            {
                throw new ArgumentNullException(nameof(codeActions));
            }

            // Used to identify if this is VSCode which doesn't support
            // code action resolve.
            if (!context.SupportsCodeActionResolve)
            {
                return(EmptyResult);
            }

            var results = codeActions.Where(codeAction =>
                                            StringMatchCodeActions.Contains(codeAction.Title) ||
                                            RegexMatchCodeActions.Any(pattern => pattern.Match(codeAction.Title).Success)
                                            );

            var wrappedResults = results.Select(c => c.WrapResolvableCSharpCodeAction(context)).ToList();

            return(Task.FromResult(wrappedResults as IReadOnlyList <RazorCodeAction>));
        }
Ejemplo n.º 8
0
        private static WorkspaceEdit CreateRenameTagEdit(RazorCodeActionContext context, MarkupStartTagSyntax startTag, string newTagName)
        {
            var changes = new List <TextEdit>
            {
                new TextEdit
                {
                    Range   = startTag.Name.GetRange(context.CodeDocument.Source),
                    NewText = newTagName,
                },
            };

            var endTag = (startTag.Parent as MarkupElementSyntax).EndTag;

            if (endTag != null)
            {
                changes.Add(new TextEdit
                {
                    Range   = endTag.Name.GetRange(context.CodeDocument.Source),
                    NewText = newTagName,
                });
            }

            return(new WorkspaceEdit
            {
                Changes = new Dictionary <Uri, IEnumerable <TextEdit> > {
                    [context.Request.TextDocument.Uri] = changes,
                }
            });
        }
Ejemplo n.º 9
0
        public override Task <IReadOnlyList <RazorCodeAction> > ProvideAsync(
            RazorCodeActionContext context,
            IEnumerable <RazorCodeAction> codeActions,
            CancellationToken cancellationToken)
        {
            if (context is null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (codeActions is null)
            {
                throw new ArgumentNullException(nameof(codeActions));
            }

            if (context.Request?.Context?.Diagnostics is null)
            {
                return(EmptyResult);
            }

            if (codeActions is null || !codeActions.Any())
            {
                return(EmptyResult);
            }

            var results = context.SupportsCodeActionResolve ?
                          ProcessCodeActionsVS(context, codeActions) :
                          ProcessCodeActionsVSCode(context, codeActions);

            var orderedResults = results.OrderBy(codeAction => codeAction.Title).ToArray();

            return(Task.FromResult(orderedResults as IReadOnlyList <RazorCodeAction>));
        }
Ejemplo n.º 10
0
        private void AddComponentAccessFromTag(RazorCodeActionContext context, MarkupStartTagSyntax startTag, List <RazorCodeAction> container)
        {
            var matching = FindMatchingTagHelpers(context, startTag);

            // For all the matches, add options for add @using and fully qualify
            foreach (var tagHelperPair in matching.Values)
            {
                if (tagHelperPair._fullyQualified is null)
                {
                    continue;
                }

                var fullyQualifiedName = tagHelperPair._short.Name;

                // Insert @using
                if (AddUsingsCodeActionProviderHelper.TryCreateAddUsingResolutionParams(fullyQualifiedName, context.Request.TextDocument.Uri, out var @namespace, out var resolutionParams))
                {
                    var addUsingCodeAction = RazorCodeActionFactory.CreateAddComponentUsing(@namespace, resolutionParams);
                    container.Add(addUsingCodeAction);
                }

                // Fully qualify
                var renameTagWorkspaceEdit   = CreateRenameTagEdit(context, startTag, fullyQualifiedName);
                var fullyQualifiedCodeAction = RazorCodeActionFactory.CreateFullyQualifyComponent(fullyQualifiedName, renameTagWorkspaceEdit);
                container.Add(fullyQualifiedCodeAction);
            }
        }
        private static RazorCodeActionContext CreateRazorCodeActionContext(CodeActionParams 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()), MockBehavior.Strict);

            var sourceText = SourceText.From(text);

            var context = new RazorCodeActionContext(request, documentSnapshot, codeDocument, location, sourceText, supportsFileCreation, supportsCodeActionResolve: true);

            return(context);
        }
Ejemplo n.º 12
0
            static SyntaxNode FindImplicitOrExplicitExpressionNode(RazorCodeActionContext context)
            {
                var change     = new SourceChange(context.Location.AbsoluteIndex, length: 0, newText: string.Empty);
                var syntaxTree = context.CodeDocument.GetSyntaxTree();

                if (syntaxTree?.Root is null)
                {
                    return(null);
                }

                var owner = syntaxTree.Root.LocateOwner(change);

                if (owner == null)
                {
                    Debug.Fail("Owner should never be null.");
                    return(null);
                }

                // E.g, (| is position)
                //
                // `@|foo` - true
                // `@(|foo)` - true
                //
                return(owner.AncestorsAndSelf().FirstOrDefault(n => n is CSharpImplicitExpressionSyntax || n is CSharpExplicitExpressionSyntax));
            }
 public override Task <IReadOnlyList <RazorCodeAction> > ProvideAsync(RazorCodeActionContext context, IEnumerable <RazorCodeAction> codeActions, CancellationToken cancellationToken)
 {
     return(Task.FromResult(new List <RazorCodeAction>()
     {
         new RazorCodeAction()
     } as IReadOnlyList <RazorCodeAction>));
 }
        public override Task <IReadOnlyList <RazorCodeAction> > ProvideAsync(
            RazorCodeActionContext context,
            IEnumerable <RazorCodeAction> codeActions,
            CancellationToken cancellationToken)
        {
            if (context is null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (codeActions is null)
            {
                throw new ArgumentNullException(nameof(codeActions));
            }

            // Used to identify if this is VSCode which doesn't support
            // code action resolve.
            if (!context.SupportsCodeActionResolve)
            {
                return(EmptyResult);
            }

            var results = new List <RazorCodeAction>();

            foreach (var codeAction in codeActions)
            {
                if (SupportedDefaultCodeActionNames.Contains(codeAction.Name))
                {
                    results.Add(codeAction.WrapResolvableCSharpCodeAction(context));
                }
            }

            return(Task.FromResult(results as IReadOnlyList <RazorCodeAction>));
        }
Ejemplo n.º 15
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
            });
        }
Ejemplo n.º 16
0
        private void AddCreateComponentFromTag(RazorCodeActionContext context, MarkupStartTagSyntax startTag, List <CommandOrCodeAction> container)
        {
            var path = context.Request.TextDocument.Uri.GetAbsoluteOrUNCPath();

            path = _filePathNormalizer.Normalize(path);
            var newComponentPath = Path.Combine(Path.GetDirectoryName(path), $"{startTag.Name.Content}.razor");

            if (File.Exists(newComponentPath))
            {
                return;
            }

            var actionParams = new CreateComponentCodeActionParams
            {
                Uri  = context.Request.TextDocument.Uri,
                Path = newComponentPath,
            };
            var data = JObject.FromObject(actionParams);

            var resolutionParams = new RazorCodeActionResolutionParams
            {
                Action = LanguageServerConstants.CodeActions.CreateComponentFromTag,
                Data   = data,
            };
            var serializedParams = JToken.FromObject(resolutionParams);
            var arguments        = new JArray(serializedParams);

            container.Add(new CommandOrCodeAction(new Command
            {
                Title     = "Create component from tag",
                Name      = LanguageServerConstants.RazorCodeActionRunnerCommand,
                Arguments = arguments,
            }));
        }
        private void AddComponentAccessFromTag(RazorCodeActionContext context, MarkupStartTagSyntax startTag, List <RazorCodeAction> container)
        {
            var matching = FindMatchingTagHelpers(context, startTag);

            // For all the matches, add options for add @using and fully qualify
            foreach (var tagHelperPair in matching.Values)
            {
                if (tagHelperPair.FullyQualified is null)
                {
                    continue;
                }

                var fullyQualifiedName = tagHelperPair.Short.Name;

                // Insert @using
                var addUsingCodeAction = AddUsingsCodeActionProviderFactory.CreateAddUsingCodeAction(
                    fullyQualifiedName,
                    context.Request.TextDocument.Uri);
                if (addUsingCodeAction != null)
                {
                    container.Add(addUsingCodeAction);
                }

                // Fully qualify
                container.Add(new RazorCodeAction()
                {
                    Title = $"{fullyQualifiedName}",
                    Edit  = CreateRenameTagEdit(context, startTag, fullyQualifiedName),
                });
            }
        }
Ejemplo n.º 18
0
        private void AddCreateComponentFromTag(RazorCodeActionContext context, MarkupStartTagSyntax startTag, List <RazorCodeAction> container)
        {
            var path = context.Request.TextDocument.Uri.GetAbsoluteOrUNCPath();

            path = _filePathNormalizer.Normalize(path);
            var newComponentPath = Path.Combine(Path.GetDirectoryName(path), $"{startTag.Name.Content}.razor");

            if (File.Exists(newComponentPath))
            {
                return;
            }

            var actionParams = new CreateComponentCodeActionParams
            {
                Uri  = context.Request.TextDocument.Uri,
                Path = newComponentPath,
            };

            var resolutionParams = new RazorCodeActionResolutionParams
            {
                Action = LanguageServerConstants.CodeActions.CreateComponentFromTag,
                Data   = actionParams,
            };

            container.Add(new RazorCodeAction()
            {
                Title = CreateComponentFromTagTitle,
                Data  = resolutionParams
            });
        }
Ejemplo n.º 19
0
        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);
        }
Ejemplo n.º 20
0
 private static RazorCodeAction CreateAddUsingCodeAction(
     RazorCodeActionContext context,
     string fullyQualifiedName)
 {
     return(AddUsingsCodeActionProviderFactory.CreateAddUsingCodeAction(
                fullyQualifiedName,
                context.Request.TextDocument.Uri));
 }
Ejemplo n.º 21
0
        public override Task <IReadOnlyList <RazorCodeAction> > ProvideAsync(
            RazorCodeActionContext context,
            IEnumerable <RazorCodeAction> codeActions,
            CancellationToken cancellationToken)
        {
            if (context is null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (codeActions is null)
            {
                throw new ArgumentNullException(nameof(codeActions));
            }

            // Used to identify if this is VSCode which doesn't support
            // code action resolve.
            if (!context.SupportsCodeActionResolve)
            {
                return(EmptyResult);
            }

            if (context.Request?.Context?.Diagnostics is null)
            {
                return(EmptyResult);
            }

            var diagnostics = context.Request.Context.Diagnostics.Where(diagnostic =>
                                                                        diagnostic.Severity == DiagnosticSeverity.Error &&
                                                                        diagnostic.Code?.IsString == true)
                              .Select(diagnostic => diagnostic.Code.Value.String)
                              .ToImmutableHashSet();

            if (diagnostics is null)
            {
                return(null);
            }

            var results = new List <RazorCodeAction>();

            if (diagnostics.Contains(ImplementAbstractClassDiagnostic))
            {
                var implementAbstractClassCodeAction = codeActions.Where(c =>
                                                                         c.Title == ImplementAbstractClassCodeActionTitle);
                results.AddRange(implementAbstractClassCodeAction);
            }

            if (diagnostics.Contains(ImplementInterfaceDiagnostic))
            {
                var implementInterfaceCodeActions = codeActions.Where(c =>
                                                                      ImplementInterfaceCodeActionTitle.Contains(c.Title));
                results.AddRange(implementInterfaceCodeActions);
            }

            var wrappedResults = results.Select(c => c.WrapResolvableCSharpCodeAction(context)).ToList();

            return(Task.FromResult(wrappedResults as IReadOnlyList <RazorCodeAction>));
        }
        private static IEnumerable <RazorCodeAction> ProcessCodeActionsVS(
            RazorCodeActionContext context,
            IEnumerable <RazorCodeAction> codeActions)
        {
            var typeAccessibilityCodeActions = new List <RazorCodeAction>(1);

            foreach (var codeAction in codeActions)
            {
                if (codeAction.Name.Equals(RazorPredefinedCodeFixProviderNames.FullyQualify, StringComparison.Ordinal))
                {
                    string action;

                    if (!TryGetOwner(context, out var owner))
                    {
                        // Failed to locate a valid owner for the light bulb
                        continue;
                    }
                    else if (IsSingleLineDirectiveNode(owner))
                    {
                        // Don't support single line directives
                        continue;
                    }
                    else if (IsExplicitExpressionNode(owner))
                    {
                        // Don't support explicit expressions
                        continue;
                    }
                    else if (IsImplicitExpressionNode(owner))
                    {
                        action = LanguageServerConstants.CodeActions.UnformattedRemap;
                    }
                    else
                    {
                        // All other scenarios we support default formatted code action behavior
                        action = LanguageServerConstants.CodeActions.Default;
                    }

                    typeAccessibilityCodeActions.Add(codeAction.WrapResolvableCSharpCodeAction(context, action));
                }
                // For add using suggestions, the code action title is of the form:
                // `using System.Net;`
                else if (codeAction.Name.Equals(RazorPredefinedCodeFixProviderNames.AddImport, StringComparison.Ordinal) &&
                         AddUsingsCodeActionProviderHelper.TryExtractNamespace(codeAction.Title, out var @namespace))
                {
                    var newCodeAction = codeAction with {
                        Title = $"@using {@namespace}"
                    };
                    typeAccessibilityCodeActions.Add(newCodeAction.WrapResolvableCSharpCodeAction(context, LanguageServerConstants.CodeActions.AddUsing));
                }
                // Not a type accessibility code action
                else
                {
                    continue;
                }
            }

            return(typeAccessibilityCodeActions);
 public override Task <IReadOnlyList <RazorCodeAction> > ProvideAsync(RazorCodeActionContext context, CancellationToken cancellationToken)
 {
     // O# Code Actions don't have `Data`, but `Commands` do
     return(Task.FromResult(new List <RazorCodeAction>()
     {
         new RazorCodeAction()
         {
             Title = "SomeTitle",
             Data = JToken.FromObject(new AddUsingsCodeActionParams())
         }
     } as IReadOnlyList <RazorCodeAction>));
 }
Ejemplo n.º 24
0
        private static IEnumerable <RazorCodeAction> ProcessCodeActionsVS(
            RazorCodeActionContext context,
            IEnumerable <RazorCodeAction> codeActions)
        {
            var typeAccessibilityCodeActions = new List <RazorCodeAction>(1);

            foreach (var codeAction in codeActions)
            {
                if (codeAction.Name.Equals(RazorPredefinedCodeFixProviderNames.FullyQualify, StringComparison.Ordinal))
                {
                    var    node = FindImplicitOrExplicitExpressionNode(context);
                    string action;

                    // The formatting pass of our Default code action resolver rejects
                    // implicit/explicit expressions. So if we're in an implicit expression,
                    // we run the remapping resolver responsible for simply remapping
                    // (without formatting) the resolved code action. We do not support
                    // explicit expressions due to issues with the remapping methodology
                    // risking document corruption.
                    if (node is null)
                    {
                        action = LanguageServerConstants.CodeActions.Default;
                    }
                    else if (node is CSharpImplicitExpressionSyntax)
                    {
                        action = LanguageServerConstants.CodeActions.UnformattedRemap;
                    }
                    else
                    {
                        continue;
                    }

                    typeAccessibilityCodeActions.Add(codeAction.WrapResolvableCSharpCodeAction(context, action));
                }
                // For add using suggestions, the code action title is of the form:
                // `using System.Net;`
                else if (codeAction.Name.Equals(RazorPredefinedCodeFixProviderNames.AddImport, StringComparison.Ordinal) &&
                         AddUsingsCodeActionProviderFactory.TryExtractNamespace(codeAction.Title, out var @namespace))
                {
                    var newCodeAction = codeAction with {
                        Title = $"@using {@namespace}"
                    };
                    typeAccessibilityCodeActions.Add(newCodeAction.WrapResolvableCSharpCodeAction(context, LanguageServerConstants.CodeActions.AddUsing));
                }
                // Not a type accessibility code action
                else
                {
                    continue;
                }
            }

            return(typeAccessibilityCodeActions);
        private async Task <IEnumerable <CodeAction> > GetCSharpCodeActionsAsync(RazorCodeActionContext context, CancellationToken cancellationToken)
        {
            var csharpCodeActions = await GetCSharpCodeActionsFromLanguageServerAsync(context, cancellationToken);

            if (csharpCodeActions is null || !csharpCodeActions.Any())
            {
                return(null);
            }

            var filteredCSharpCodeActions = await FilterCSharpCodeActionsAsync(context, csharpCodeActions, cancellationToken);

            return(filteredCSharpCodeActions);
        }
 private static bool TryProcessCodeAction(
     RazorCodeActionContext context,
     CodeAction codeAction,
     Diagnostic diagnostic,
     string associatedValue,
     out ICollection <CodeAction> typeAccessibilityCodeActions)
 {
     // VS & VSCode provide type accessibility code actions in different formats
     // We must handle them seperately.
     return(context.SupportsCodeActionResolve ?
            TryProcessCodeActionVS(context, codeAction, diagnostic, associatedValue, out typeAccessibilityCodeActions) :
            TryProcessCodeActionVSCode(context, codeAction, diagnostic, associatedValue, out typeAccessibilityCodeActions));
 }
Ejemplo n.º 27
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);
        }
Ejemplo n.º 28
0
 private bool IsTagUnknown(MarkupStartTagSyntax startTag, RazorCodeActionContext context)
 {
     foreach (var diagnostic in context.CodeDocument.GetCSharpDocument().Diagnostics)
     {
         // Check that the diagnostic is to do with our start tag
         if (!(diagnostic.Span.AbsoluteIndex > startTag.Span.End || startTag.Span.Start > diagnostic.Span.AbsoluteIndex + diagnostic.Span.Length))
         {
             // Component is not recognized in environment
             if (diagnostic.Id == ComponentDiagnosticFactory.UnexpectedMarkupElement.Id)
             {
                 return(true);
             }
         }
     }
     return(false);
 }
Ejemplo n.º 29
0
        private void AddComponentAccessFromTag(RazorCodeActionContext context, MarkupStartTagSyntax startTag, List <CommandOrCodeAction> container)
        {
            var matching = FindMatchingTagHelpers(context, startTag);

            // For all the matches, add options for add @using and fully qualify
            foreach (var tagHelperPair in matching.Values)
            {
                if (tagHelperPair.FullyQualified is null)
                {
                    continue;
                }

                var fullyQualifiedComponentName = tagHelperPair.Short.Name;  // We assume .Name is the fully qualified component name
                DefaultRazorTagHelperBinderPhase.ComponentDirectiveVisitor.TrySplitNamespaceAndType(fullyQualifiedComponentName, out var namespaceSpan, out var _);
                var namespaceName = tagHelperPair.Short.Name.Substring(namespaceSpan.Start, namespaceSpan.Length);
                var actionParams  = new AddUsingsCodeActionParams
                {
                    Uri       = context.Request.TextDocument.Uri,
                    Namespace = namespaceName,
                };
                var data = JObject.FromObject(actionParams);

                var resolutionParams = new RazorCodeActionResolutionParams
                {
                    Action = LanguageServerConstants.CodeActions.AddUsing,
                    Data   = data,
                };
                var serializedParams = JToken.FromObject(resolutionParams);
                var arguments        = new JArray(serializedParams);

                // Insert @using
                container.Add(new CommandOrCodeAction(new Command
                {
                    Title     = $"@using {namespaceName}",
                    Name      = LanguageServerConstants.RazorCodeActionRunnerCommand,
                    Arguments = arguments,
                }));

                // Fully qualify
                container.Add(new CommandOrCodeAction(new CodeAction
                {
                    Title = $"{tagHelperPair.Short.Name}",
                    Edit  = CreateRenameTagEdit(context, startTag, tagHelperPair.Short.Name),
                }));
            }
        }
Ejemplo n.º 30
0
        private async Task <IEnumerable <RazorCodeAction> > GetRazorCodeActionsAsync(RazorCodeActionContext context, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();

            var tasks = new List <Task <IReadOnlyList <RazorCodeAction> > >();

            foreach (var provider in _razorCodeActionProviders)
            {
                var result = provider.ProvideAsync(context, cancellationToken);
                if (result != null)
                {
                    tasks.Add(result);
                }
            }

            return(await ConsolidateCodeActionsFromProvidersAsync(tasks, cancellationToken));
        }