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