-
Notifications
You must be signed in to change notification settings - Fork 470
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CA2263: Prefer generic overload when type is known (#6857)
This analyzer detects when a `System.Type` overload is called when a suitable generic overload is available. To validate if a generic overload is applicable, the arity, parameter count, containing symbol of the invocation (to avoid endless loops), return type, argument types and type constraints(using speculative binding) are checked. The fixer removes unnecessary casts and parentheses.
- Loading branch information
Showing
26 changed files
with
3,296 additions
and
2 deletions.
There are no files selected for viewing
90 changes: 90 additions & 0 deletions
90
...tAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Usage/CSharpPreferGenericOverloads.Fixer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
|
||
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()); | ||
} | ||
} | ||
} |
65 changes: 65 additions & 0 deletions
65
src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Usage/CSharpPreferGenericOverloads.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
55 changes: 55 additions & 0 deletions
55
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Usage/PreferGenericOverloads.Fixer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
Oops, something went wrong.