Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -78,27 +78,35 @@ public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilitie
(service, solutionInfo, cancellationToken) => service.GetCodeActionRequestInfoAsync(solutionInfo, razorDocument.Id, request, cancellationToken),
cancellationToken).ConfigureAwait(false);

if (requestInfo is null or { LanguageKind: RazorLanguageKind.CSharp, CSharpRequest: null })
if (requestInfo is null ||
requestInfo is { LanguageKind: RazorLanguageKind.CSharp, CSharpRequest: null, CSharpDeclRequest: null })
{
return null;
}

// This is just to prevent a warning for an unused field in the VS Code extension
Debug.Assert(_requestInvoker is not null);

var delegatedCodeActions = requestInfo.LanguageKind switch
{
// We don't support Html code actions in VS Code
#if !VSCODE
RazorLanguageKind.Html => await GetHtmlCodeActionsAsync(razorDocument, request, correlationId, cancellationToken).ConfigureAwait(false),
var htmlCodeActions = requestInfo.LanguageKind == RazorLanguageKind.Html
? await GetHtmlCodeActionsAsync(razorDocument, request, correlationId, cancellationToken).ConfigureAwait(false)
: [];
#else
// We don't support Html code actions in VS Code
var htmlCodeActions = Array.Empty<RazorVSInternalCodeAction>();
#endif
RazorLanguageKind.CSharp => await GetCSharpCodeActionsAsync(razorDocument, requestInfo.CSharpRequest.AssumeNotNull(), correlationId, cancellationToken).ConfigureAwait(false),
_ => []
};

var csharpCodeActions = requestInfo is { LanguageKind: RazorLanguageKind.CSharp, CSharpRequest: { } csharpRequest }
? await GetCSharpCodeActionsAsync(razorDocument, csharpRequest, correlationId, cancellationToken).ConfigureAwait(false)
: [];

var csharpDeclCodeActions = requestInfo is { LanguageKind: RazorLanguageKind.CSharp, CSharpDeclRequest: { } csharpDeclRequest }
? await GetCSharpCodeActionsAsync(razorDocument, csharpDeclRequest, correlationId, cancellationToken).ConfigureAwait(false)
: [];

return await _remoteServiceInvoker.TryInvokeAsync<IRemoteCodeActionsService, SumType<Command, CodeAction>[]?>(
razorDocument.Project.Solution,
(service, solutionInfo, cancellationToken) => service.GetCodeActionsAsync(solutionInfo, razorDocument.Id, request, delegatedCodeActions, cancellationToken),
(service, solutionInfo, cancellationToken) => service.GetCodeActionsAsync(solutionInfo, razorDocument.Id, request, htmlCodeActions, csharpCodeActions, csharpDeclCodeActions, cancellationToken),
cancellationToken).ConfigureAwait(false);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ public static DocumentPositionInfo GetPositionInfo(
var languageKind = codeDocument.GetLanguageKind(razorIndex, rightAssociative: false);
if (languageKind is RazorLanguageKind.CSharp)
{
if (service.TryMapToCSharpDocumentLinePosition(codeDocument, razorIndex, out var mappedPosition, out _, out inDeclDocument))
if (service.TryMapToCSharpDocumentLinePosition(codeDocument, razorIndex, out var mappedPosition, out _, out var isInDeclDocument))
{
inDeclDocument = isInDeclDocument;
// For C# locations, we attempt to return the corresponding position
// within the projected document
position = mappedPosition.ToPosition();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Task<ImmutableArray<TextChange>> GetCSharpOnTypeFormattingChangesAsync(
CancellationToken cancellationToken);

Task<TextChange?> TryGetCSharpCodeActionEditAsync(
DocumentContext documentContext,
IDocumentSnapshot documentSnapshot,
ImmutableArray<TextChange> csharpEdits,
bool declarationDocument,
RazorFormattingOptions options,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,6 @@ public static class CodeActions

public const string WrapAttributes = nameof(WrapAttributes);

/// <summary>
/// Remaps without formatting the resolved code action edit
/// </summary>
public const string UnformattedRemap = nameof(UnformattedRemap);

/// <summary>
/// Remaps and formats the resolved code action edit
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ namespace Microsoft.CodeAnalysis.Razor.Remote;

internal record CodeActionRequestInfo(
[property: JsonPropertyName("languageKind")] RazorLanguageKind LanguageKind,
[property: JsonPropertyName("csharpRequest")] VSCodeActionParams? CSharpRequest);
[property: JsonPropertyName("csharpRequest")] VSCodeActionParams? CSharpRequest,
[property: JsonPropertyName("csharpDeclRequest")] VSCodeActionParams? CSharpDeclRequest);
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ ValueTask<CodeActionRequestInfo> GetCodeActionRequestInfoAsync(
JsonSerializableRazorSolutionWrapper solutionInfo,
JsonSerializableDocumentId razorDocumentId,
VSCodeActionParams request,
RazorVSInternalCodeAction[] delegatedCodeActions,
RazorVSInternalCodeAction[] htmlCodeActions,
RazorVSInternalCodeAction[] csharpCodeActions,
RazorVSInternalCodeAction[] csharpDeclCodeActions,
CancellationToken cancellationToken);

ValueTask<CodeAction> ResolveCodeActionAsync(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.LanguageServer;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.CodeActions;
Expand Down Expand Up @@ -61,53 +61,50 @@ public async Task<CodeAction> ResolveAsync(
continue;
}

var editDocumentContext = await CreateDocumentContextAsync(snapshot, generatedDocumentUri, cancellationToken).ConfigureAwait(false);
if (editDocumentContext is null)
// We know this is a virtual C# file, but we have to jump through a couple of hoops to make sure we get the right info
var solution = snapshot.TextDocument.Project.Solution;

var razorDocument = await _snapshotManager.TryGetRazorDocumentAsync(solution, generatedDocumentUri, cancellationToken).ConfigureAwait(false);
if (razorDocument is null)
{
_logger.LogWarning($"Could not get razor document for {generatedDocumentUri} processing {codeAction.Title}, so leaving original edit in place.");
continue;
}

var razorSnapshot = _snapshotManager.GetSnapshot(razorDocument);
var codeDocument = await razorSnapshot.GetGeneratedOutputAsync(cancellationToken).ConfigureAwait(false);

if (!solution.TryGetSourceGeneratedDocumentIdentity(generatedDocumentUri, out var identity))
{
_logger.LogWarning($"Could not create document context for {generatedDocumentUri} processing {codeAction.Title}, so leaving original edit in place.");
_logger.LogWarning($"Could not get generated document identity for {generatedDocumentUri} processing {codeAction.Title}, so leaving original edit in place.");
continue;
}

var csharpSourceText = await editDocumentContext.GetCSharpSourceTextAsync(cancellationToken).ConfigureAwait(false);
var csharpDocument = codeDocument.GetCSharpDocumentForHintName(identity.HintName);
var csharpSourceText = csharpDocument.Text;
var csharpTextChanges = textDocumentEdit.Edits.SelectAsArray(e => csharpSourceText.GetTextChange((TextEdit)e));

// Remaps the text edits from the generated C# to the razor file,
// as well as applying appropriate formatting.
var formattedChange = await _razorFormattingService.TryGetCSharpCodeActionEditAsync(
editDocumentContext,
razorSnapshot,
csharpTextChanges,
declarationDocument: false, // PROTOTYPE(sonic): Pass in the right value to this
declarationDocument: csharpDocument.IsDeclarationDocument,
formattingOptions,
cancellationToken).ConfigureAwait(false);

if (formattedChange is { } change)
{
var sourceText = await editDocumentContext.GetSourceTextAsync(cancellationToken).ConfigureAwait(false);
textDocumentEdit.TextDocument = new() { DocumentUri = editDocumentContext.Uri };
textDocumentEdit.Edits = [sourceText.GetTextEdit(change)];
textDocumentEdit.TextDocument = new() { DocumentUri = razorDocument.GetURI() };
textDocumentEdit.Edits = [codeDocument.Source.Text.GetTextEdit(change)];
}
else
{
_logger.LogWarning($"Formatting dropped all C# code edits for {codeAction.Title} in {editDocumentContext.Uri}");
_logger.LogWarning($"Formatting dropped all C# code edits for {codeAction.Title} in {razorDocument.GetURI()}");
textDocumentEdit.Edits = [];
}
}

return codeAction;
}

private async Task<RemoteDocumentContext?> CreateDocumentContextAsync(RemoteDocumentSnapshot originDocumentSnapshot, Uri generatedDocumentUri, CancellationToken cancellationToken)
{
var razorDocument = await _snapshotManager.TryGetRazorDocumentAsync(
originDocumentSnapshot.TextDocument.Project.Solution,
generatedDocumentUri,
cancellationToken).ConfigureAwait(false);
if (razorDocument is null)
{
return null;
}

var razorDocumentSnapshot = _snapshotManager.GetSnapshot(razorDocument);
return new RemoteDocumentContext(razorDocument.GetURI(), razorDocumentSnapshot);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Frozen;
using System.Collections.Generic;
Comment thread
davidwengier marked this conversation as resolved.
using System.Collections.Immutable;
using System.Composition;
Expand Down Expand Up @@ -29,8 +30,7 @@ namespace Microsoft.CodeAnalysis.Remote.Razor.CodeActions;
[Export(typeof(ICSharpCodeActionProvider)), Shared]
internal sealed class TypeAccessibilityCodeActionProvider : ICSharpCodeActionProvider
{
private static readonly IEnumerable<string> s_supportedDiagnostics = new[]
{
private static readonly FrozenSet<string> s_supportedDiagnostics = FrozenSet.Create(StringComparer.OrdinalIgnoreCase, [
// `The type or namespace name 'type/namespace' could not be found
// (are you missing a using directive or an assembly reference?)`
// https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/cs0246
Expand All @@ -42,7 +42,7 @@ internal sealed class TypeAccessibilityCodeActionProvider : ICSharpCodeActionPro

// `The name 'identifier' does not exist in the current context`
"IDE1007"
};
]);

public Task<ImmutableArray<RazorVSInternalCodeAction>> ProvideAsync(
RazorCodeActionContext context,
Expand Down Expand Up @@ -74,7 +74,7 @@ private static ImmutableArray<RazorVSInternalCodeAction> ProcessCodeActionsVSCod
var diagnostics = context.Request.Context.Diagnostics.Where(diagnostic =>
diagnostic is { Severity: LspDiagnosticSeverity.Error, Code: { } code } &&
code.TryGetSecond(out var str) &&
s_supportedDiagnostics.Any(d => str.Equals(d, StringComparison.OrdinalIgnoreCase)));
s_supportedDiagnostics.Contains(str));

if (diagnostics is null || !diagnostics.Any())
{
Expand Down Expand Up @@ -163,50 +163,29 @@ private static ImmutableArray<RazorVSInternalCodeAction> ProcessCodeActionsVS(

foreach (var codeAction in codeActions)
{
// For fully qualify, we just want to filter out single line expressions as the formatter doesn't support them and will
// drop the edits
if (codeAction.Name is not null && codeAction.Name.Equals(PredefinedCodeFixProviderNames.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))
if (TryGetOwner(context, out var owner) &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (TryGetOwner(context, out var owner) &&

Probably doesn't matter, but there is a logical change here in that the old code would continue the loop if TryGetOwner return false.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, this was deliberate on my part. My thinking was that previously we needed the the owner to filter out a number of scenarios because what we supported was quite narrow. Now we only care about one thing, and unless we're in that state, we let the normal system handle things, so if we can't get an owner, then we can't be in the one state we care about not offering for, so let it through.

In reality I suspect owner will never be null.

IsSingleLineDirectiveNode(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.WrapResolvableCodeAction(context, action));
}
// For add using suggestions, the code action title is of the form:
// `using System.Net;`
// For add using suggestions, we make the title more Razor-idiomatic
else if (codeAction.Name is not null && codeAction.Name.Equals(PredefinedCodeFixProviderNames.AddImport, StringComparison.Ordinal) &&
UsingDirectiveHelper.TryExtractNamespace(codeAction.Title, out var @namespace, out var prefix))
{
codeAction.Title = $"{prefix}@using {@namespace}";
typeAccessibilityCodeActions.Add(codeAction.WrapResolvableCodeAction(context, LanguageServerConstants.CodeActions.Default));
}
// Not a type accessibility code action
else
{
continue;
}

// Type accessibility code actions need no special handling at edit time, so we just send them through the standard C# resolver
typeAccessibilityCodeActions.Add(codeAction.WrapResolvableCodeAction(context, LanguageServerConstants.CodeActions.Default));
}

return typeAccessibilityCodeActions.ToImmutable();
Expand All @@ -229,24 +208,6 @@ static bool TryGetOwner(RazorCodeActionContext context, [NotNullWhen(true)] out
return true;
}

static bool IsImplicitExpressionNode(SyntaxNode owner)
{
// E.g, (| is position)
//
// `@|foo` - true
//
return owner.AncestorsAndSelf().Any(n => n is CSharpImplicitExpressionSyntax);
}

static bool IsExplicitExpressionNode(SyntaxNode owner)
{
// E.g, (| is position)
//
// `@(|foo)` - true
//
return owner.AncestorsAndSelf().Any(n => n is CSharpExplicitExpressionBodySyntax);
}

static bool IsSingleLineDirectiveNode(SyntaxNode owner)
{
// E.g, (| is position)
Expand Down

This file was deleted.

Loading