-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Create NameArguments code fixer (15.later) #22010
Conversation
Lol... i was right in the middle of this. Let me see your version :) #Closed |
@@ -104,6 +104,7 @@ public static class Features | |||
public const string CodeActionsUseCollectionInitializer = "CodeActions.UseCollectionInitializer"; | |||
public const string CodeActionsUseDefaultLiteral = "CodeActions.UseDefaultLiteral"; | |||
public const string CodeActionsUseInferredMemberName = "CodeActions.UseInferredMemberName"; | |||
public const string CodeActionsNameLiteralArgument = "CodeActions.UseNamedArgument"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be sorted properly. #Closed
|
||
namespace Microsoft.CodeAnalysis.CSharp.NameLiteralArgument | ||
{ | ||
[ExportCodeFixProvider(LanguageNames.CSharp), Shared] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs a name. #Resolved
var argument = (ArgumentSyntax)root.FindNode(diagnostic.Location.SourceSpan); | ||
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); | ||
|
||
var parameterName = GetParameterName(argument, semanticModel); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it would be faster to just store hte parameter name with the diagnostic properties so we don't need to go retrieve a semantic model here. #Resolved
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); | ||
|
||
var parameterName = GetParameterName(argument, semanticModel); | ||
var newArgument = SyntaxFactory.Argument(SyntaxFactory.NameColon(parameterName), default, argument.Expression); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you could just do argument.WithNameColon. Also, you should probably remove teh trivia from the original argument and add it to the new argument. #Resolved
} | ||
|
||
int index = arguments.IndexOf(argument); | ||
var parameters = semanticModel.GetSymbolInfo(argument.Parent.Parent).Symbol.GetParameters(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is assuming a lot. #Resolved
public override bool OpenFileOnly(Workspace workspace) | ||
=> false; | ||
|
||
protected override void InitializeWorker(AnalysisContext context) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not just register for SyntaxKind.Argument? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seemed cheaper (only check the options and get the symbol once per invocation). #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, that's somewhat reasonable :) #Closed
{ | ||
return; | ||
} | ||
if (!optionSet.GetOption(CSharpCodeStyleOptions.PreferNamingLiteralArguments).Value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
newline. #Resolved
|
||
var node = context.Node; | ||
SeparatedSyntaxList<ArgumentSyntax> arguments; | ||
switch (node) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicated code with fix provider. Either make extension, or make into a static helper here that both can use. #Resolved
return; | ||
} | ||
|
||
var symbolInfo = context.SemanticModel.GetSymbolInfo(context.Node); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you have a local for context.Node below. use that. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
insteafd of storing into symbolInfo. Just do a .Symbol here and store into 'var symbol'. #Resolved
continue; | ||
} | ||
|
||
if (parameters[i].IsParams) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Slightly concerned about error cases (though that shouldn't probably happen if .Symbol was non-null). But what if there are less parameters than arguments? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: combine all three checks into one. #Resolved
} | ||
} | ||
|
||
private bool IsPositionalArgument(ArgumentSyntax argument) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: use => #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: doesn't seem useful enough to have it's own method. #Resolved
<value>Prefere local function over anonymous function</value> | ||
<value>Prefer local function over anonymous function</value> | ||
</data> | ||
<data name="Prefer_naming_literal_arguments" xml:space="preserve"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i feel like we might want to put in the name (C# 7.2 and above). The reason here is that the feature could make sense even in C# 7.1 and below, but we're choosing not to offer it because we don't want to then name all the subsequent parameters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the option should be: "Prefer named literal arguments (C# 7.2 and greater)"
You shoudl be able to share a ton of code here between C# and VB. #Closed |
Two medium issues for me.
|
{ | ||
M(a, /*before*/ b: 2 /*after*/, /*before*/ c: 3 /*after*/); | ||
} | ||
}", parseOptions: s_parseOptions); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
need multiline tests. #Resolved
|
||
var parameterName = GetParameterName(argument, semanticModel); | ||
var newArgument = SyntaxFactory.Argument(SyntaxFactory.NameColon(parameterName), default, argument.Expression); | ||
editor.ReplaceNode(argument, newArgument); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this cahnges to a feature where users can add named args in more places (i.e. 'always' or 'for_lambdas') then this won't be right. Consider:
Foo(Bar(5))
there are two named arguments that can be added to make: Foo(arg: Bar(val: 5))
. To support this, you need to call the .ReplaceNode overload that takes a lambda and gives you the current node, so that you see the changes of nested updates.
{ | ||
public CSharpNameLiteralArgumentDiagnosticAnalyzer() | ||
: base(IDEDiagnosticIds.NameLiteralArgumentDiagnosticId, | ||
new LocalizableResourceString(nameof(FeaturesResources.Name_literal_argument), FeaturesResources.ResourceManager, typeof(FeaturesResources))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should have two resource strings. One should be something like "Prefer named literal argument". It is what will go in the error list. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is still needed. #Resolved
return; | ||
} | ||
|
||
var parameters = symbolInfo.Symbol.GetParameters(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
then you won't ahve to do symbolInfo.Symbol here. #Resolved
continue; | ||
} | ||
|
||
context.ReportDiagnostic( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because you have the parameter here, you can just grab the name and add it to the Properties object for the diagnostic. This will save expensive work on the code fix provider side. #Resolved
@@ -154,6 +154,12 @@ internal static class CSharpCodeStyleOptions | |||
EditorConfigStorageLocation.ForBoolCodeStyleOption("csharp_prefer_inferred_anonymous_type_member_names"), | |||
new RoamingProfileStorageLocation($"TextEditor.CSharp.Specific.{nameof(PreferInferredAnonymousTypeMemberNames)}")}); | |||
|
|||
public static readonly Option<CodeStyleOption<bool>> PreferNamingLiteralArguments = new Option<CodeStyleOption<bool>>( | |||
nameof(CodeStyleOptions), nameof(PreferNamingLiteralArguments), defaultValue: CodeStyleOptions.TrueWithSuggestionEnforcement, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should not be on by default. Or it should be true with none enforcement. We don't want tons of ...'s showing up in people's code. #Resolved
public static readonly Option<CodeStyleOption<bool>> PreferNamingLiteralArguments = new Option<CodeStyleOption<bool>>( | ||
nameof(CodeStyleOptions), nameof(PreferNamingLiteralArguments), defaultValue: CodeStyleOptions.TrueWithSuggestionEnforcement, | ||
storageLocations: new OptionStorageLocation[] { | ||
EditorConfigStorageLocation.ForBoolCodeStyleOption("csharp_prefer_naming_literal_arguments"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dotnet_style_prefer_named_literal_arguments (since this will be supported for Vb right? i thought you said VB had non-trailing arg support).
That said, i think i would prefer that this was:
dotnet_style_prefer_named_arguments=always,never,for_literals,for_... (i.e. for_lambdas, for_etc).
This leaves us open for extending this in the future. bools make that harder. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If supported for VB, should move to CodeStyleOptions #Resolved
continue; | ||
} | ||
|
||
if (!argument.Expression.IsAnyLiteralExpression()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: this extension needs to be updated to handle 'default' literals. (please add test as well).
Also, consider removing extension entirely. I'm not sure i see a purpose in it given that you can write "is LiteralExpressionSyntax". While kind checks are faster than typechecks, this has to check like 8 kinds. At that point I doubt it is any faster. #Resolved
Done with pass. #Closed |
No VB implementation yet
📝 I'm still working on the code style view model, to be able to display the choices. |
@@ -793,6 +810,7 @@ internal StyleViewModel(OptionSet optionSet, IServiceProvider serviceProvider) : | |||
CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferInferredTupleNames, ServicesVSResources.Prefer_inferred_tuple_names, s_preferInferredTupleName, s_preferInferredTupleName, this, optionSet, expressionPreferencesGroupTitle)); | |||
CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferInferredAnonymousTypeMemberNames, ServicesVSResources.Prefer_inferred_anonymous_type_member_names, s_preferInferredAnonymousTypeMemberName, s_preferInferredAnonymousTypeMemberName, this, optionSet, expressionPreferencesGroupTitle)); | |||
CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferLocalOverAnonymousFunction, ServicesVSResources.Prefer_local_function_over_anonymous_function, s_preferLocalFunctionOverAnonymousFunction, s_preferLocalFunctionOverAnonymousFunction, this, optionSet, expressionPreferencesGroupTitle)); | |||
CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CodeStyleOptions.PreferNamedArguments, ServicesVSResources.Prefer_named_literal_arguments_CSharp_7_2_or_greater, s_preferNamedArguments, s_preferNamedArguments, this, optionSet, expressionPreferencesGroupTitle)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@CyrusNajmabadi
I discussed how to structure the options with DavidP.
We're thinking it may be better to stick with boolean options (rather than enum options), but have more than one (one for literals, one for lambdas, one for everything) when we want, under one section.
Then it would look like the section for "var" preferences:
In the meantime, it would be a single boolean option for literals.
This would also solve a technical problem. When I tried to fix the code above (since BooleanCodeStyleOptionViewModel
because I pass an option that's wrong), the constructor for EnumCodeStyleOptionViewModel
does not seem to handle per-language options, but only plain options.
Let me know if that approach seems ok.
I defer all decisions to @dpoeschl :) That said, while it would absolutely be expedient to represent this as booleans for our current impl, we may want to consider if that really makes sense for the future. I think we should design the editor config options to make the most sense in isolation (which, to me, means allowing "prefer_named_arguments=for_literals,for_anonymous_functions", and then figure out a good UI for this. For example, i believe resharper suports this through drop down lists that have checkboxes in them. NOte that the UI can come in the future. since you're not currently doing "for_anonymous_functions" we wouldn't have to worry about how to make the UI work here. |
a586378
to
1fe2655
Compare
@CyrusNajmabadi Thanks for the clarification. I'm assuming that the desired behavior on FixAll on a literal argument is to let the user choose what they want to do: (1) name all literal arguments (in document/projects/solution), or (2) name all arguments (also in various scopes). |
Paging @sharwell who usually knows of exotic FixAlls or otherwise has thoughts on them. |
From discussion with @heejaechang, @rchande and @kuhlenh: From discussion with Kasey, if we have three code style preferences (literals, lambdas, all), the latter one should be off by default to avoid spamming the lightbulb when invoked on a line, and to play nice with some future "apply all code styles" capability. Then we have to decide what to do about the existing "Add argument name" refactoring. Kasey will see if it is used. (1) We could just remove it. Or (2) we could make it conditional (don't trigger if the diagnostic from the code analyzer/fixer is there, assuming that the refactoring has access to that information). |
I'm not sure if that's a problem. i mean the refactoring already always offers this. So it's no worse than today. To me, this entire feature makes much more sense just as a single analyzer. i.e. we remove the existing refactoring and do everything as one analyzer. The analyzer always runs and always allows you to trigger the "add named arg" feature if in a valid location. All that the options control is if we show things up as squiggles/dots/error-list messages (depending on the location and waht option you picked). |
I think that's workable. The only downside of removing the refactoring is that we lose the ability to add argument names including trailing names. There is still the question of behavior for FixAll. Would the following make sense?
|
Why? The analyzer would still just mark the nodes in the question. The codefixprovider for that analyzer could offerboth fixes. |
We have flexibility here. Probably what makes most sense is to have FixAll behavior offered and contingent upon your settings. So if you have "Prefer named arguments for XXX = true" and you fix-all then we fix up all the things that match your preference. By default you would have no preference, and thus fix-all woudl not be offered. Once you indicate your preferences, then you get fixall |
I'm primarily concerned about the limitations of the current option. In particular, we're providing a Fix All that's going to get some "colorful" feedback regarding the changes to this code: var list = new List<int>();
list.Add(0);
list.Add(1); As for the limitation to literals:I believe this limitation makes sense for the first implementation, but I can imagine numerous scenarios where we may want to expand it. For example, As for the code fix/refactoring:
Future workThis feature should be defined in a way that it can be expanded to support an option to remove unnecessary arguments. This may tie into the same preference regarding the use of named literals, or it could use a new option. The default value for the setting would be suggesting the removal of explicit argument names in cases where the rules for tuple element name inference would have inferred the same name. |
@jcouv When you get this working, if at all possible please create a branch in your Roslyn fork and execute the Fix All demonstrating the impact of the default behavior on this repository. You may need to temporarily remove the C# 7.2 limitation in the analysis to get it to pass but it should allow you to create the branch. |
@sharwell Thanks for the feedback. Last commit removes "literal" from the presentation and setting name. I'm now working on the refactoring, so that it doesn't trigger when the analyzer already reports a diagnostic. |
From discussion with Jason, we should not have a code fixer and a refactoring implementing similar logic. I'm going to try and convert the existing refactoring into a fixer, and then have the analyzer produce diagnostics for literal, but also produce diagnostics for any argument that could be named. @mavasani Jason recommend that I validate this design with you. Adding diagnostics for every argument that could be named seems like a lot... I'll stop by to discuss. Thanks |
Closed because I found out that in practice the existing refactoring is sufficient. Also, my experiment with running such a fixer on the Roslyn codebase was actually rather annoying (consider all the |
Adds a code style (Prefer_naming_literal_arguments`), an analyzer to complain about un-named literal arguments that could be named, and a fixer to rectify the situation.
Fixes #19186
@CyrusNajmabadi FYI
I plan to add support for attributes (they use different syntax) and VB, as the "AddNamedArgument" refactoring does. But I don't expect I'll be able to share much code, although I'm open to the possibility.