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),
                });
            }
        }
Exemple #2
0
 private static RazorCodeAction CreateAddUsingCodeAction(
     RazorCodeActionContext context,
     string fullyQualifiedName)
 {
     return(AddUsingsCodeActionProviderFactory.CreateAddUsingCodeAction(
                fullyQualifiedName,
                context.Request.TextDocument.Uri));
 }
        public void GetNamespaceFromFQN_Valid_ReturnsNamespace()
        {
            // Arrange
            var fqn = "Abc.Xyz";

            // Act
            var namespaceName = AddUsingsCodeActionProviderFactory.GetNamespaceFromFQN(fqn);

            // Assert
            Assert.Equal("Abc", namespaceName);
        }
        public void GetNamespaceFromFQN_Invalid_ReturnsEmpty()
        {
            // Arrange
            var fqn = "Abc";

            // Act
            var namespaceName = AddUsingsCodeActionProviderFactory.GetNamespaceFromFQN(fqn);

            // Assert
            Assert.Empty(namespaceName);
        }
        public void TryExtractNamespace_WithStatic_ReturnsTruue()
        {
            // Arrange
            var csharpAddUsing = "using static X.Y.Z;";

            // Act
            var res = AddUsingsCodeActionProviderFactory.TryExtractNamespace(csharpAddUsing, out var @namespace);

            // Assert
            Assert.True(res);
            Assert.Equal("static X.Y.Z", @namespace);
        }
        public void TryExtractNamespace_ReturnsTrue()
        {
            // Arrange
            var csharpAddUsing = "using Abc.Xyz;";

            // Act
            var res = AddUsingsCodeActionProviderFactory.TryExtractNamespace(csharpAddUsing, out var @namespace);

            // Assert
            Assert.True(res);
            Assert.Equal("Abc.Xyz", @namespace);
        }
        public void TryExtractNamespace_Invalid_ReturnsFalse()
        {
            // Arrange
            var csharpAddUsing = "Abc.Xyz;";

            // Act
            var res = AddUsingsCodeActionProviderFactory.TryExtractNamespace(csharpAddUsing, out var @namespace);

            // Assert
            Assert.False(res);
            Assert.Empty(@namespace);
        }
        public void CreateAddUsingCodeAction_CreatesCodeAction()
        {
            // Arrange
            var fqn    = "Abc.Xyz";
            var docUri = DocumentUri.From("c:/path");

            // Act
            var codeAction = AddUsingsCodeActionProviderFactory.CreateAddUsingCodeAction(fqn, docUri);

            // Assert
            Assert.Equal("@using Abc", codeAction.Title);
        }
Exemple #9
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 static bool TryProcessCodeActionVSCode(
            RazorCodeActionContext context,
            CodeAction codeAction,
            Diagnostic diagnostic,
            string associatedValue,
            out ICollection <CodeAction> 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;
            }
            // When there are multiple FQN suggestions, the code action title is of the form:
            // `Fully qualify 'Dns' -> System.Net.Dns`
            else
            {
                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 <CodeAction>();

            var fqnCodeAction = CreateFQNCodeAction(context, diagnostic, codeAction, fqn);

            typeAccessibilityCodeActions.Add(fqnCodeAction);

            var addUsingCodeAction = AddUsingsCodeActionProviderFactory.CreateAddUsingCodeAction(fqn, context.Request.TextDocument.Uri);

            if (addUsingCodeAction != null)
            {
                typeAccessibilityCodeActions.Add(addUsingCodeAction);
            }

            return(true);
        }
        private static bool TryProcessCodeActionVS(
            RazorCodeActionContext context,
            CodeAction codeAction,
            Diagnostic diagnostic,
            string associatedValue,
            out ICollection <CodeAction> typeAccessibilityCodeActions)
        {
            CodeAction processedCodeAction = null;

            // 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))
            {
                var fqn = codeAction.Title;
                processedCodeAction = CreateFQNCodeAction(context, diagnostic, codeAction, fqn);
            }
            // When there are multiple FQN suggestions, the code action title is of the form:
            // `Fully qualify 'Dns'`
            else if (codeAction.Title.Equals($"Fully qualify '{associatedValue}'", StringComparison.OrdinalIgnoreCase))
            {
                // Not currently supported as we need O# CodeAction to support the CodeAction.Children field.
                // processedCodeAction = codeAction.WrapResolvableCSharpCodeAction(context, LanguageServerConstants.CodeActions.FullyQualifyType);

                typeAccessibilityCodeActions = Array.Empty <CodeAction>();
                return(false);
            }
            // For add using suggestions, the code action title is of the form:
            // `using System.Net;`
            else if (AddUsingsCodeActionProviderFactory.TryExtractNamespace(codeAction.Title, out var @namespace))
            {
                codeAction.Title    = $"@using {@namespace}";
                processedCodeAction = codeAction.WrapResolvableCSharpCodeAction(context, LanguageServerConstants.CodeActions.AddUsing);
            }
            // Not a type accessibility code action
            else
            {
                typeAccessibilityCodeActions = Array.Empty <CodeAction>();
                return(false);
            }

            typeAccessibilityCodeActions = new[] { processedCodeAction };
            return(true);
        }
Exemple #12
0
        public async override Task <CodeAction?> ResolveAsync(
            CSharpCodeActionParams csharpParams,
            CodeAction codeAction,
            CancellationToken cancellationToken)
        {
            if (csharpParams is null)
            {
                throw new ArgumentNullException(nameof(csharpParams));
            }

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

            cancellationToken.ThrowIfCancellationRequested();

            var resolvedCodeAction = await ResolveCodeActionWithServerAsync(csharpParams.RazorFileUri, codeAction, cancellationToken).ConfigureAwait(false);

            if (resolvedCodeAction?.Edit?.DocumentChanges is null)
            {
                // Unable to resolve code action with server, return original code action
                return(codeAction);
            }

            if (resolvedCodeAction.Edit.DocumentChanges.Count() != 1)
            {
                // We don't yet support multi-document code actions, return original code action
                return(codeAction);
            }

            var documentChanged = resolvedCodeAction.Edit.DocumentChanges.First();

            if (!documentChanged.IsTextDocumentEdit)
            {
                // Only Text Document Edit changes are supported currently, return original code action
                return(codeAction);
            }

            var addUsingTextEdit = documentChanged.TextDocumentEdit?.Edits.FirstOrDefault();

            if (addUsingTextEdit is null)
            {
                // No text edit available
                return(codeAction);
            }

            if (!AddUsingsCodeActionProviderFactory.TryExtractNamespace(addUsingTextEdit.NewText, out var @namespace))
            {
                // Invalid text edit, missing namespace
                return(codeAction);
            }

            var documentSnapshot = await _projectSnapshotManagerDispatcher.RunOnDispatcherThreadAsync(() =>
            {
                _documentResolver.TryResolveDocument(csharpParams.RazorFileUri.GetAbsoluteOrUNCPath(), out var documentSnapshot);
                return(documentSnapshot);
            }, cancellationToken).ConfigureAwait(false);

            if (documentSnapshot is null)
            {
                return(codeAction);
            }

            var text = await documentSnapshot.GetTextAsync().ConfigureAwait(false);

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

            var codeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false);

            if (codeDocument.IsUnsupported())
            {
                return(null);
            }

            var documentVersion = await _projectSnapshotManagerDispatcher.RunOnDispatcherThreadAsync(() =>
            {
                _documentVersionCache.TryGetDocumentVersion(documentSnapshot, out var version);
                return(version);
            }, cancellationToken).ConfigureAwait(false);

            var codeDocumentIdentifier = new OptionalVersionedTextDocumentIdentifier()
            {
                Uri     = csharpParams.RazorFileUri,
                Version = documentVersion.Value
            };

            var edit = AddUsingsCodeActionResolver.CreateAddUsingWorkspaceEdit(@namespace, codeDocument, codeDocumentIdentifier);

            resolvedCodeAction = resolvedCodeAction with {
                Edit = edit
            };

            return(resolvedCodeAction);
        }
    }
Exemple #13
0
        private static IEnumerable <RazorCodeAction> ProcessCodeActionsVSCode(
            RazorCodeActionContext context,
            IEnumerable <RazorCodeAction> codeActions)
        {
            var diagnostics = context.Request.Context.Diagnostics.Where(diagnostic =>
                                                                        diagnostic.Severity == DiagnosticSeverity.Error &&
                                                                        diagnostic.Code?.IsString == true &&
                                                                        SupportedDiagnostics.Any(d => diagnostic.Code.Value.String.Equals(d, StringComparison.OrdinalIgnoreCase)));

            if (diagnostics is null || !diagnostics.Any())
            {
                return(Array.Empty <RazorCodeAction>());
            }

            var typeAccessibilityCodeActions = new List <RazorCodeAction>();

            foreach (var diagnostic in diagnostics)
            {
                // Corner case handling for diagnostics which (momentarily) linger after
                // @code block is cleared out
                if (diagnostic.Range.End.Line > context.SourceText.Lines.Count ||
                    diagnostic.Range.End.Character > context.SourceText.Lines[diagnostic.Range.End.Line].End)
                {
                    continue;
                }

                var diagnosticSpan = diagnostic.Range.AsTextSpan(context.SourceText);

                // Based on how we compute `Range.AsTextSpan` it's possible to have a span
                // which goes beyond the end of the source text. Something likely changed
                // between the capturing of the diagnostic (by the platform) and the retrieval of the
                // document snapshot / source text. In such a case, we skip processing of the diagnostic.
                if (diagnosticSpan.End > context.SourceText.Length)
                {
                    continue;
                }

                foreach (var codeAction in codeActions)
                {
                    if (!codeAction.Name.Equals(LanguageServerConstants.CodeActions.CodeActionFromVSCode, StringComparison.Ordinal))
                    {
                        continue;
                    }

                    var associatedValue = context.SourceText.GetSubTextString(diagnosticSpan);

                    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;
                    }
                    // When there are multiple FQN suggestions, the code action title is of the form:
                    // `Fully qualify 'Dns' -> System.Net.Dns`
                    else
                    {
                        var expectedCodeActionPrefix = $"Fully qualify '{associatedValue}' -> ";
                        if (codeAction.Title.StartsWith(expectedCodeActionPrefix, StringComparison.OrdinalIgnoreCase))
                        {
                            fqn = codeAction.Title.Substring(expectedCodeActionPrefix.Length);
                        }
                    }

                    if (string.IsNullOrEmpty(fqn))
                    {
                        continue;
                    }

                    var fqnCodeAction = CreateFQNCodeAction(context, diagnostic, codeAction, fqn);
                    typeAccessibilityCodeActions.Add(fqnCodeAction);

                    var addUsingCodeAction = AddUsingsCodeActionProviderFactory.CreateAddUsingCodeAction(fqn, context.Request.TextDocument.Uri);
                    if (addUsingCodeAction != null)
                    {
                        typeAccessibilityCodeActions.Add(addUsingCodeAction);
                    }
                }
            }

            return(typeAccessibilityCodeActions);
        }