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

CA1854: Use unused variable name for out parameter #7261

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -142,10 +142,13 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
typeSyntax = IdentifierName(Var);
}

var identifierName = (IdentifierNameSyntax)(variableName is not null
? generator.IdentifierName(variableName)
: generator.FirstUnusedIdentifierName(model, containsKeyInvocation.SpanStart, Value));
var outArgument = (ArgumentSyntax)generator.Argument(RefKind.Out,
DeclarationExpression(
typeSyntax,
SingleVariableDesignation(Identifier(variableName ?? Value))
SingleVariableDesignation(identifierName.Identifier)
)
);

Expand All @@ -154,7 +157,6 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
.AddArgumentListArguments(outArgument);
editor.ReplaceNode(containsKeyInvocation, tryGetValueInvocation);

var identifierName = (IdentifierNameSyntax)generator.IdentifierName(variableName ?? Value);
if (addStatementNode != null)
{
editor.InsertBefore(addStatementNode,
Expand Down
Expand Up @@ -498,6 +498,32 @@ bool GetDict(out IDictionary<string, int> dict)
}
return 0;";

private const string GuardedReturnIdentifierUsed = @"
int value = 0;
int value1 = 1;
int value2 = 2;
string key = ""key"";
ConcurrentDictionary<string, int> data = new ConcurrentDictionary<string, int>();
if ({|#0:data.ContainsKey(key)|})
{
return {|#1:data[key]|};
}

return 0;";

private const string GuardedReturnIdentifierUsedFixed = @"
int value = 0;
int value1 = 1;
int value2 = 2;
string key = ""key"";
ConcurrentDictionary<string, int> data = new ConcurrentDictionary<string, int>();
if (data.TryGetValue(key, out int value3))
{
return value3;
}

return 0;";

#region NoDiagnostic

private const string InvalidModifiedBeforeUse = @"
Expand Down Expand Up @@ -1136,6 +1162,33 @@ Dim y
End If
Return 0";

private const string VbGuardedReturnIdentifierUsed = @"
Dim value As Integer = 0
Dim value1 As Integer = 1
Dim value2 As Integer = 2
Dim key As String = ""key""
Dim data As ConcurrentDictionary(Of String, Integer) = New ConcurrentDictionary(Of String, Integer)()

If {|#0:data.ContainsKey(key)|} Then
Return {|#1:data(key)|}
End If

Return 0";

private const string VbGuardedReturnIdentifierUsedFixed = @"
Dim value As Integer = 0
Dim value1 As Integer = 1
Dim value2 As Integer = 2
Dim key As String = ""key""
Dim data As ConcurrentDictionary(Of String, Integer) = New ConcurrentDictionary(Of String, Integer)()

Dim value3 As Integer = Nothing
If data.TryGetValue(key, value3) Then
Return value3
End If

Return 0";

#region NoDiagnostic

private const string VbInvalidModifiedBeforeUse = @"
Expand Down Expand Up @@ -1299,6 +1352,7 @@ End If
[InlineData(GuardedKeyInSimpleAssignment, GuardedKeyInSimpleAssignmentFixed)]
[InlineData(GuardedInlineVariable, GuardedInlineVariableFixed)]
[InlineData(GuardedInlineVariable2, GuardedInlineVariable2Fixed)]
[InlineData(GuardedReturnIdentifierUsed, GuardedReturnIdentifierUsedFixed)]
public Task ShouldReportDiagnostic(string codeSnippet, string fixedCodeSnippet, int additionalLocations = 1)
{
string testCode = CreateCSharpCode(codeSnippet);
Expand Down Expand Up @@ -1372,6 +1426,7 @@ public Task ShouldNotReportDiagnostic(string codeSnippet, LanguageVersion versio
[InlineData(VbGuardedKeyInSimpleAssignment, VbGuardedKeyInSimpleAssignmentFixed)]
[InlineData(VbGuardedInlineVariable, VbGuardedInlineVariableFixed)]
[InlineData(VbGuardedInlineVariable2, VbGuardedInlineVariable2Fixed)]
[InlineData(VbGuardedReturnIdentifierUsed, VbGuardedReturnIdentifierUsedFixed)]
public Task VbShouldReportDiagnostic(string codeSnippet, string fixedCodeSnippet, int additionalLocations = 1)
{
string testCode = CreateVbCode(codeSnippet);
Expand Down
@@ -1,6 +1,7 @@
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

Imports System.Threading
Imports Analyzer.Utilities
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.CodeActions
Imports Microsoft.CodeAnalysis.CodeFixes
Expand Down Expand Up @@ -112,20 +113,22 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Performance
Async Function(ct As CancellationToken) As Task(Of Document)
Dim editor = Await DocumentEditor.CreateAsync(document, ct).ConfigureAwait(False)
Dim generator = editor.Generator
If variableName Is Nothing Then
variableName = Value
End If

Dim identifierName = DirectCast(If(variableName Is Nothing,
generator.FirstUnusedIdentifierName(semanticModel,
containsKeyAccess.SpanStart,
Value),
generator.IdentifierName(variableName)),
IdentifierNameSyntax)
Dim tryGetValueAccess = generator.MemberAccessExpression(containsKeyAccess.Expression,
TryGetValue)
Dim keyArgument = containsKeyInvocation.ArgumentList.Arguments.FirstOrDefault()
Dim valueAssignment =
generator.LocalDeclarationStatement(dictionaryValueType,
variableName,
identifierName.Identifier.ValueText,
generator.DefaultExpression(dictionaryValueType)).
WithLeadingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed).
WithoutTrailingTrivia()
Dim identifierName As SyntaxNode = generator.IdentifierName(variableName)
Dim tryGetValueInvocation = generator.InvocationExpression(tryGetValueAccess,
keyArgument,
generator.Argument(identifierName))
Expand Down
34 changes: 34 additions & 0 deletions src/Utilities/Workspaces/SyntaxGeneratorExtensions.cs
Expand Up @@ -455,5 +455,39 @@ public static SyntaxNode DefaultMethodStatement(this SyntaxGenerator generator,

return node;
}

/// <summary>
/// Creates the first unused identifier name based on the provided base name.
/// </summary>
/// <param name="generator">The <see cref="SyntaxGenerator"/> used to create the identifier name.</param>
/// <param name="semanticModel">The semantic model.</param>
/// <param name="position">The position in the code.</param>
/// <param name="baseName">The base name to use.</param>
/// <param name="maxTries">Maximum number of tries.</param>
/// <returns>
/// A <see cref="SyntaxNode"/> representing an unused identifier name.
/// This can be either the base name itself or a variation of it with a number appended to make it unique.
/// </returns>
public static SyntaxNode FirstUnusedIdentifierName(this SyntaxGenerator generator, SemanticModel semanticModel, int position, string baseName, int maxTries = int.MaxValue)
{
var identifierName = generator.IdentifierName(baseName);

if (semanticModel.GetSpeculativeSymbolInfo(position, identifierName, SpeculativeBindingOption.BindAsExpression).Symbol is null)
{
return identifierName;
}

for (int i = 1; i < maxTries; i++)
{
identifierName = generator.IdentifierName($"{baseName}{i}");

if (semanticModel.GetSpeculativeSymbolInfo(position, identifierName, SpeculativeBindingOption.BindAsExpression).Symbol is null)
{
break;
}
}

return identifierName;
}
}
}