diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpPreferDictionaryTryMethodsOverContainsKeyGuardFixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpPreferDictionaryTryMethodsOverContainsKeyGuardFixer.cs index 1a7148916b..61379505e6 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpPreferDictionaryTryMethodsOverContainsKeyGuardFixer.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpPreferDictionaryTryMethodsOverContainsKeyGuardFixer.cs @@ -98,9 +98,6 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) var editor = await DocumentEditor.CreateAsync(document, ct).ConfigureAwait(false); var generator = editor.Generator; - var tryGetValueAccess = generator.MemberAccessExpression(containsKeyAccess.Expression, TryGetValue); - var keyArgument = containsKeyInvocation.ArgumentList.Arguments.FirstOrDefault(); - // Roslyn has reducers that are run after a code action is applied, one of which will // simplify a TypeSyntax to `var` if the user prefers that. So we generate TypeSyntax, add // simplifier annotation, and then let Roslyn decide whether to keep TypeSyntax or convert it to var. @@ -119,13 +116,16 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) typeSyntax = IdentifierName(Var); } - var outArgument = generator.Argument(RefKind.Out, + var outArgument = (ArgumentSyntax)generator.Argument(RefKind.Out, DeclarationExpression( typeSyntax, SingleVariableDesignation(Identifier(Value)) ) ); - var tryGetValueInvocation = generator.InvocationExpression(tryGetValueAccess, keyArgument, outArgument); + + var tryGetValueInvocation = containsKeyInvocation + .ReplaceNode(containsKeyAccess.Name, IdentifierName(TryGetValue).WithTriviaFrom(containsKeyAccess.Name)) + .AddArgumentListArguments(outArgument); editor.ReplaceNode(containsKeyInvocation, tryGetValueInvocation); var identifierName = (IdentifierNameSyntax)generator.IdentifierName(Value); diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/PreferDictionaryTryGetValueMethodsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/PreferDictionaryTryGetValueMethodsTests.cs index 2b70c2ac7d..f6aabac626 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/PreferDictionaryTryGetValueMethodsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/PreferDictionaryTryGetValueMethodsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. using System.Collections.Generic; using System.Globalization; @@ -1605,6 +1605,55 @@ public void Test(int key, string text) { return VerifyCS.VerifyCodeFixAsync(code, diagnostic, fixedCode); } + [Fact] + [WorkItem(7098, "https://github.com/dotnet/roslyn-analyzers/issues/7098")] + public async Task CodeFixPreservesStyle() + { + const string code = + """ + using System.Collections.Generic; + + namespace UnitTests { + class Program { + Dictionary dictionary = new(); + public void Test(int key, string text) { + if({|#0:this.dictionary.ContainsKey(key)|} && !string.IsNullOrEmpty(text)) { + text = {|#1:dictionary[key]|}; + } + } + } + } + """; + const string fixedCode = + """ + using System.Collections.Generic; + + namespace UnitTests { + class Program { + Dictionary dictionary = new(); + public void Test(int key, string text) { + if(this.dictionary.TryGetValue(key, out string value) && !string.IsNullOrEmpty(text)) { + text = value; + } + } + } + } + """; + + await new VerifyCS.Test + { + LanguageVersion = LanguageVersion.CSharp9, + TestCode = code, + FixedCode = fixedCode, + ExpectedDiagnostics = + { + VerifyCS.Diagnostic(PreferDictionaryTryMethodsOverContainsKeyGuardAnalyzer.PreferTryGetValueDiagnostic) + .WithLocation(0) + .WithLocation(1), + }, + }.RunAsync(); + } + private static string CreateCSharpCode(string content) { return string.Format(CultureInfo.InvariantCulture, CSharpTemplate, content);