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

CA2263: Prefer generic overload when type is known #6857

Merged
merged 4 commits into from
Feb 16, 2024
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
@@ -0,0 +1,90 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.CodeAnalysis.Simplification;
using Microsoft.NetCore.Analyzers.Usage;
using static Microsoft.NetCore.Analyzers.Usage.PreferGenericOverloadsAnalyzer;

namespace Microsoft.NetCore.CSharp.Analyzers.Usage
{
/// <summary>
/// CA2263: <inheritdoc cref="NetCore.Analyzers.MicrosoftNetCoreAnalyzersResources.PreferGenericOverloadsTitle"/>
/// </summary>
[ExportCodeFixProvider(LanguageNames.CSharp), Shared]
public sealed class CSharpPreferGenericOverloadsFixer : PreferGenericOverloadsFixer
{
protected override async Task<Document> ReplaceWithGenericCallAsync(Document document, IInvocationOperation invocation, CancellationToken cancellationToken)
{
if (!RuntimeTypeInvocationContext.TryGetContext(invocation, out var invocationContext))
{
return document;
}
mpidash marked this conversation as resolved.
Show resolved Hide resolved

var modifiedInvocationSyntax = CSharpPreferGenericOverloadsAnalyzer.GetModifiedInvocationSyntax(invocationContext);

if (modifiedInvocationSyntax is not InvocationExpressionSyntax invocationExpressionSyntax)
{
return document;
}

// Analyzers are not allowed to have a reference to Simplifier, so add the additional annotation here instead.
invocationExpressionSyntax = invocationExpressionSyntax.WithExpression(invocationExpressionSyntax.Expression.WithAdditionalAnnotations(Simplifier.Annotation));

var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);

if (invocationContext.Parent is IConversionOperation conversionOperation
&& invocationContext.Parent.Syntax is CastExpressionSyntax castExpressionSyntax
&& invocationContext.SemanticModel is not null)
{
var typeInfo = invocationContext.SemanticModel.GetSpeculativeTypeInfo(
invocationContext.Syntax.SpanStart,
invocationExpressionSyntax,
SpeculativeBindingOption.BindAsExpression);

if (typeInfo.ConvertedType.IsAssignableTo(conversionOperation.Type, invocationContext.SemanticModel.Compilation))
{
// Add a simplifier annotation to the parent to remove no longer needed parenthesis.
if (castExpressionSyntax.Parent is ParenthesizedExpressionSyntax parenthesizedExpressionSyntax)
{
editor.ReplaceNode(
parenthesizedExpressionSyntax,
parenthesizedExpressionSyntax
.ReplaceNode(
castExpressionSyntax,
castExpressionSyntax.Expression
.ReplaceNode(invocationContext.Syntax, invocationExpressionSyntax)
.WithTriviaFrom(castExpressionSyntax))
.WithAdditionalAnnotations(Simplifier.Annotation));
}
else
{
editor.ReplaceNode(
castExpressionSyntax,
castExpressionSyntax.Expression
.ReplaceNode(invocationContext.Syntax, invocationExpressionSyntax)
.WithTriviaFrom(castExpressionSyntax));
}
}
else
{
editor.ReplaceNode(invocationContext.Syntax, invocationExpressionSyntax);
}
}
else
{
editor.ReplaceNode(invocationContext.Syntax, invocationExpressionSyntax);
}

return document.WithSyntaxRoot(editor.GetChangedRoot());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.NetCore.Analyzers.Usage;
using Microsoft.CodeAnalysis.Operations;

namespace Microsoft.NetCore.CSharp.Analyzers.Usage
{
/// <summary>
/// CA2263: <inheritdoc cref="NetCore.Analyzers.MicrosoftNetCoreAnalyzersResources.PreferGenericOverloadsTitle"/>
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class CSharpPreferGenericOverloadsAnalyzer : PreferGenericOverloadsAnalyzer
{
protected sealed override bool TryGetModifiedInvocationSyntax(RuntimeTypeInvocationContext invocationContext, [NotNullWhen(true)] out SyntaxNode? modifiedInvocationSyntax)
{
modifiedInvocationSyntax = GetModifiedInvocationSyntax(invocationContext);

return modifiedInvocationSyntax is not null;
}

// Expose as internal static to allow the fixer to also call this method.
internal static SyntaxNode? GetModifiedInvocationSyntax(RuntimeTypeInvocationContext invocationContext)
{
if (invocationContext.Syntax is not InvocationExpressionSyntax invocationSyntax)
{
return null;
}

var typeArgumentsSyntax = invocationContext.TypeArguments.Select(t => SyntaxFactory.ParseTypeName(t.ToDisplayString()));
var otherArgumentsSyntax = invocationContext.OtherArguments
.Where(a => a.ArgumentKind != ArgumentKind.DefaultValue)
.Select(a => a.Syntax)
.OfType<ArgumentSyntax>();
var methodNameSyntax =
SyntaxFactory.GenericName(
SyntaxFactory.Identifier(invocationContext.Method.Name),
SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList(typeArgumentsSyntax)));
var modifiedInvocationExpression = invocationSyntax.Expression;

if (modifiedInvocationExpression is MemberAccessExpressionSyntax memberAccessExpressionSyntax)
{
modifiedInvocationExpression = memberAccessExpressionSyntax.WithName(methodNameSyntax);
}
else if (modifiedInvocationExpression is IdentifierNameSyntax identifierNameSyntax)
{
modifiedInvocationExpression = methodNameSyntax;
}
else
{
return null;
}

return invocationSyntax
.WithExpression(modifiedInvocationExpression)
.WithArgumentList(SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(otherArgumentsSyntax)))
.WithTriviaFrom(invocationSyntax);
}
}
}
1 change: 1 addition & 0 deletions src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ Rule ID | Category | Severity | Notes
CA1514 | Maintainability | Info | AvoidLengthCheckWhenSlicingToEndAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1514)
CA1515 | Maintainability | Disabled | MakeTypesInternal, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1515)
CA2262 | Usage | Info | ProvideHttpClientHandlerMaxResponseHeaderLengthValueCorrectly, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2262)
CA2263 | Usage | Info | PreferGenericOverloadsAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2263)
Original file line number Diff line number Diff line change
Expand Up @@ -1913,6 +1913,18 @@ Widening and user defined conversions are not supported with generic types.</val
<data name="ImplementGenericMathInterfacesCorrectlyTitle" xml:space="preserve">
<value>Use correct type parameter</value>
</data>
<data name="PreferGenericOverloadsCodeFixTitle" xml:space="preserve">
<value>Use generic overload</value>
</data>
<data name="PreferGenericOverloadsDescription" xml:space="preserve">
<value>Using a generic overload is preferable to the 'System.Type' overload when the type is known, promoting cleaner and more type-safe code with improved compile-time checks.</value>
</data>
<data name="PreferGenericOverloadsMessage" xml:space="preserve">
<value>Prefer the generic overload '{0}' instead of '{1}'</value>
</data>
<data name="PreferGenericOverloadsTitle" xml:space="preserve">
<value>Prefer generic overload when type is known</value>
</data>
<data name="UseSpanClearInsteadOfFillCodeFixTitle" xml:space="preserve">
<value>Use 'Clear()'</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Analyzer.Utilities;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Operations;
using static Microsoft.NetCore.Analyzers.Usage.PreferGenericOverloadsAnalyzer;

namespace Microsoft.NetCore.Analyzers.Usage
{
/// <summary>
/// CA2263: <inheritdoc cref="MicrosoftNetCoreAnalyzersResources.PreferGenericOverloadsTitle"/>
/// </summary>
public abstract class PreferGenericOverloadsFixer : CodeFixProvider
{
public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(RuleId);

public sealed override FixAllProvider GetFixAllProvider()
{
return WellKnownFixAllProviders.BatchFixer;
}

public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var node = root.FindNode(context.Span, getInnermostNodeForTie: true);

if (node is null)
{
return;
}

var semanticModel = await context.Document.GetRequiredSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
var operation = semanticModel.GetOperation(node, context.CancellationToken);

if (operation is not IInvocationOperation invocation)
{
return;
}

var codeAction = CodeAction.Create(
MicrosoftNetCoreAnalyzersResources.PreferGenericOverloadsCodeFixTitle,
ct => ReplaceWithGenericCallAsync(context.Document, invocation, ct),
nameof(MicrosoftNetCoreAnalyzersResources.PreferGenericOverloadsCodeFixTitle));

context.RegisterCodeFix(codeAction, context.Diagnostics);
}

protected abstract Task<Document> ReplaceWithGenericCallAsync(Document document, IInvocationOperation invocation, CancellationToken cancellationToken);
}
}
Loading