Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DRAFT] Add Code Action Provider Name to CodeAction.Data #48951

Closed
wants to merge 11 commits into from

Conversation

TanayParikh
Copy link
Contributor

This is an extremely early PR. I just wanted to confirm the general methodology is sound for adding the provider/implementation names to the CodeAction.Data payload.

This payload will be used by razor, to determine the "type" of a code action in a language agnostic way. Currently we use the English CodeAction.Title which won't work for other languages.

If the approach is fine, I'll expand to a greater set of code action providers.

For: https://github.com/dotnet/aspnetcore/issues/25939

/// <remarks>
/// The unique identifier is currently set as:
/// name of top level code action provider + '|' + name of code action provider implementation
/// e.g. 'Add Await Code Action Provider | GetTitleWithConfigureAwait'
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Mirroring format of above UniqueIdentifier

Copy link
Contributor

Choose a reason for hiding this comment

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

GetTitleWithConfigureAwait is localized, which I believe would likely be problematic if Razor is trying to avoid localization issues.

I'm wondering if ProviderName can simply be made up of PredefinedCodeFixProviderNames and PredefinedCodeRefactoringProviderNames?
This would mean that you wouldn't be able to filter out specific code actions from a provider, but it would be simpler and would avoid localization issues.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note; we're doing nameof(GetTitleWithConfigureAwait) not using the result of the call.

It appears the method name isn't localized, however the resource value is. In that case would we still run into localization issues?

With regard to making things simpler, I agree removing it would reduce the complexity. Would you prefer that approach?

My reasoning for specifying the implementation was, there may be cases where Razor may support one implementation of a provider, but not another. Granted, no specific example jumps out at me right now so this may be premature.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh whoops, my bad, I totally missed that. 😅 Yeah, I don't think using nameof should have any localization issues.

In general, I do think the simpler approach would definitely be more ideal if that works for Razor. I don't think every code action provider will have something similar to GetTitleWithConfigureAwait, for example the Add Parameter code fix provider seems to have different logic for displaying their code actions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense, thanks for the clarification! Will remove the implementation from the ProviderName.

Copy link
Contributor

Choose a reason for hiding this comment

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

You will need to define helper types say, RazorPredefinedCodeRefactoringProviderNames and RazorPredefinedCodeFixProviderNames with constant string fields that forward to the internal types PredefinedCodeRefactoringProviderNames and PredefinedCodeFixProviderNames respectively. Something similar to https://github.com/dotnet/roslyn/blob/master/src/Tools/ExternalAccess/FSharp/Diagnostics/FSharpIDEDiagnosticIds.cs. I presume the new files will go https://github.com/dotnet/roslyn/tree/master/src/Tools/ExternalAccess/Razor?

Copy link
Contributor Author

@TanayParikh TanayParikh Nov 13, 2020

Choose a reason for hiding this comment

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

What's the difference in the code (refactoring/fix) providers in the PredefinedCodeRefactoringProviderNames and PredefinedCodeFixProviderNames and something like the Add Null Checks code action we have both the
AddNullChecksId (private field) as well as the Add Null Checks Feature Resource?

Ie. For the Razor external access, should the private AddNullChecksId be used or the FeatureResources?

@mavasani

Copy link
Contributor

@allisonchou allisonchou Nov 14, 2020

Choose a reason for hiding this comment

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

For Razor external access, can just the PredefinedCodeRefactoringProviderNames and PredefinedCodeFixProviderNames be exposed? I guess I'm not understanding why AddNullChecksId or FeatureResources might be necessary to expose. For example, for AddNullChecksId, is Razor able to simply use the overarching PredefinedCodeRefactoringProviderNames.GenerateConstructorFromMembers?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For example, for AddNullChecksId, is Razor able to simply use the overarching PredefinedCodeRefactoringProviderNames.GenerateConstructorFromMembers

Ah, I wasn't aware that AddNullChecks would be a part of the GenerateConstructorFromMembers. I tried annotating the associated abstract classes of GenerateConstructorFromMembers but that didn't actually annotate the Add Null Check CodeAction.Data payload with the expected provider name. Is there a specific (inherited) code action I may have missed annotating?

Copy link
Contributor

@allisonchou allisonchou Nov 17, 2020

Choose a reason for hiding this comment

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

Ah, wait. The "Add null checks" feature resource you linked isn't actually its own separate code action, I believe it's just part of the Generate constructor dialog (see number 4 image at https://docs.microsoft.com/en-us/visualstudio/ide/reference/generate-constructor?view=vs-2019).
Sorry, didn't catch that before. It might also be important to note that Roslyn currently filters out any code actions with dialogs/options in LSP, since LSP currently doesn't support them.

When you say you want "Add null check" code action, are you possibly thinking of the action associated with the AbstractAddParameterCheckCodeRefactoringProvider? http://sourceroslyn.io/#Microsoft.CodeAnalysis.Features/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs,96

/// name of top level code action provider + '|' + name of code action provider implementation
/// e.g. 'Add Await Code Action Provider | GetTitleWithConfigureAwait'
/// </remarks>
public string ProviderName { get; }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Needs to be added to published API.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I don't think we need the ProviderName property in this file actually. The properties in CodeActionResolveData are just used to carry over necessary information from our CodeActionHandler class to our CodeActionResolveHandler class. Since CodeActionHandler is the class that tells LSP what code actions are available, I don't think we need to carry over the ProviderName information to the resolve handler, since all the resolve handler does is populate the code action edits.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wouldn't we need this property here to ensure the CodeAction.Data payload actually carries the provider name?

The properties in CodeActionResolveData are just used to carry over necessary information from our CodeActionHandler class to our CodeActionResolveHandler class.

In-between Razor get's the CodeActionResolveData and that's where I was intending to inspect the payload to consume the ProviderName. Is there a better way to ensure razor get's the provider name?

Copy link
Contributor

Choose a reason for hiding this comment

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

I might be misunderstanding how things work here. Could you explain what Razor is doing in between Roslyn's CodeActionsHandler and CodeActionResolveHandler?

My initial impression was that per our conversation with Miguel, Razor would provide Roslyn with a list ProviderNames through a custom property to Roslyn's CodeActionsHandler. Roslyn would then filter code actions based on these provider names, and only return back a filtered set of code actions. Later, Roslyn would get code action resolve requests for each of the filtered code actions, and then would populate these code actions with the edit data.
^Is this correct? Please feel free to correct me if not.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I might be misunderstanding how things work here. Could you explain what Razor is doing in between Roslyn's CodeActionsHandler and CodeActionResolveHandler?

  1. Razor sends a textDocument/codeAction request to the Roslyn CodeActionsHandler.
  2. Roslyn runs its analyzers & [NEW in this PR] annotates the response CodeAction.CodeActionResolveData with the ProviderName property.
  3. Razor receives the response from Update links in README.md with ported wiki content #2, inspects the CodeAction.CodeActionResolveData.ProviderName to identify the code action, determine whether or not it is supported, and take the appropriate action to display it to the user in a razor compatible manner.
  4. [Optional]: Should it be necessary to resolve the code action (ex. user selects it), then razor sends a textDocument/codeActionResolve request to Roslyn's CodeActionResolveHandler with the initial CodeAction.CodeActionResolveData.
  5. Roslyn returns resolved edits, razor remaps those edits & displays them to the user.

My initial impression was that per our conversation with Miguel, Razor would provide Roslyn with a list ProviderNames through a custom property to Roslyn's CodeActionsHandler. Roslyn would then filter code actions based on these provider names, and only return back a filtered set of code actions. Later, Roslyn would get code action resolve requests for each of the filtered code actions, and then would populate these code actions with the edit data.

This was definitely one of the proposals we were discussing, but we ended up deciding against it for now in the interest of simplicity and reduce coupling. This also required potential LSP Spec changes, whereas annotating the CodeActionResolveData payload does not as the spec just specifies an object Data;. The goal of this PR was to take the approach steps detailed above. I hope that helps clarify things, let me know if there are any other questions :)

Copy link
Contributor

Choose a reason for hiding this comment

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

That was a great explanation, thank you! In that case, yeah, I definitely think the data payload needs the provider name.

@TanayParikh
Copy link
Contributor Author

TanayParikh commented Nov 13, 2020

Question regarding the general design.

For some code actions we "bubble up" data like the provider name => Provider => to SimpleCodeAction => to the base CodeAction.

For other code actions, we inherit directly from the base CodeAction (ex. ImplementInterfaceCodeAction). In this case we'd want to override the virtual ProviderName we've added.

I'm just wondering if we want to keep this distinction. Personally, I'd prefer to override the ProviderName property wherever we'd like to annotate a code action with the provider name, instead of having to bubble up through various constructors. I believe this would make for a neater "annotation" mechanism. Just wanted to get your thoughts.

@dibarbet @allisonchou

@TanayParikh
Copy link
Contributor Author

/ping; hoping to get this unblocked and merged soon :)

@allisonchou
Copy link
Contributor

Personally, I'd prefer to override the ProviderName property wherever we'd like to annotate a code action with the provider name, instead of having to bubble up through various constructors. I believe this would make for a neater "annotation" mechanism.

Hmm, I think either approach seems ok here from my perspective. I think bubbling up actually looks neater to me, but overriding would definitely be more consistent.
Not sure if @mavasani might have an opinion on this?

@mavasani
Copy link
Contributor

Hmm, I think either approach seems ok here from my perspective. I think bubbling up actually looks neater to me, but overriding would definitely be more consistent.
Not sure if @mavasani might have an opinion on this?

Same here - I think all the properties on code action are similar - you can either define your own custom code action type and override the virtual properties OR pass them via the constructor.

@TanayParikh
Copy link
Contributor Author

Hmm, I think either approach seems ok here from my perspective. I think bubbling up actually looks neater to me, but overriding would definitely be more consistent.
Not sure if @mavasani might have an opinion on this?

Same here - I think all the properties on code action are similar - you can either define your own custom code action type and override the virtual properties OR pass them via the constructor.

Thanks for clarifying!

/// <remarks>
/// e.g. 'Add Await Code Action Provider'
/// </remarks>
internal virtual string? ProviderName => null;
Copy link
Contributor

Choose a reason for hiding this comment

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

We may want to consider exposing this property as a public API - this is something that can be made part of the code action telemetry and we can resolve the long outstanding issue #4919. @allisonchou do you mind bringing it to the design meeting?

@ryanbrandenburg
Copy link
Contributor

Design Meeting Notes: We should have a presentation from a knowledgeable Razor person about why we need this and what the alternatives (if any) are. Hopefully we can make that happen at next weeks design meetings.

@TanayParikh TanayParikh deleted the dev/taparik/addCodeActionProviderName branch February 19, 2021 17:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants