-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add analyzer CA1876: Prefer ReadOnlySpan #51216
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
Open
Copilot
wants to merge
28
commits into
main
Choose a base branch
from
copilot/add-readonlyspan-analyzer
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+2,326
−1
Open
Changes from 4 commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
63ddd73
Initial plan
Copilot 9895466
Add PreferReadOnlySpanOverSpan analyzer and fixer (CA1876)
Copilot f6d6050
Add unit tests for CA1876 analyzer and fixer
Copilot d1eb3fa
Address code review feedback - fix implicit interface detection and i…
Copilot 2048486
Address review feedback: Use ConcurrentDictionary, consolidate condit…
Copilot 7a671a6
Simplify fixer code with pattern matching, add comprehensive test cov…
Copilot a5c435b
Add ImplicitIndexerReference support for Index/Range operations and c…
Copilot 24bb214
Remove unused ArrayElementReference handler to avoid dead code
Copilot ed46589
Add comprehensive handling and tests for additional scenarios
Copilot 971feca
Address code review feedback: move comments, combine nested ifs, remo…
Copilot 9ea8a05
Convert first 3 tests to raw string literals as example pattern
Copilot e707884
Update src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnal…
stephentoub edab27b
Add blank line before PreferReadOnlySpanOverSpan entries in resx file
Copilot 99e22c7
Update src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnal…
stephentoub fcfd745
Fix false positive when span indexer element is passed as ref/out arg…
Copilot b891eb1
Fix false positives for ref variable declarations and ref returns
Copilot 2732125
Fix false positive for Memory/Span parameters passed via Slice() to w…
Copilot 04f9807
Fix false positive for Memory/Span parameters passed via Slice() to w…
Copilot 4fabc06
Enhance analyzer to handle fixed statements, Slice assignments, and r…
Copilot 2fa7a21
Add test for chained Slice operations with indexer increment
Copilot 11c1683
Fix build errors: use AddressOf operation instead of Fixed operation …
Copilot 23b02f5
Fix syntax error: move test method inside class definition
Copilot 53a22fd
Fix AllowUnsafeBlocks: use SolutionTransforms with WithAllowUnsafe
Copilot 7ff966b
Add missing using directive for CSharpCompilationOptions
Copilot 8f762c1
Fix PropertyReference handler to detect chained Slice operations
Copilot 3266540
Fix increment/decrement detection by adding dedicated handler
Copilot d673d72
Fix invocation handler to detect Slice() assigned through ternary exp…
Copilot 78296ea
Fix remaining test failures: addressof and ternary expression assignm…
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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
132 changes: 132 additions & 0 deletions
132
....NetAnalyzers/Microsoft.NetCore.Analyzers/Performance/PreferReadOnlySpanOverSpan.Fixer.cs
This file contains hidden or 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,132 @@ | ||
// 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.Composition; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Analyzer.Utilities; | ||
using Analyzer.Utilities.Extensions; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CodeActions; | ||
using Microsoft.CodeAnalysis.CodeFixes; | ||
using Microsoft.CodeAnalysis.Editing; | ||
|
||
namespace Microsoft.NetCore.Analyzers.Performance | ||
{ | ||
/// <summary> | ||
/// CA1876: Use ReadOnlySpan<T> or ReadOnlyMemory<T> instead of Span<T> or Memory<T> | ||
/// </summary> | ||
[ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic, Name = nameof(PreferReadOnlySpanOverSpanFixer))] | ||
stephentoub marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
[Shared] | ||
public sealed class PreferReadOnlySpanOverSpanFixer : CodeFixProvider | ||
{ | ||
public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } = | ||
ImmutableArray.Create(PreferReadOnlySpanOverSpanAnalyzer.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); | ||
var semanticModel = await context.Document.GetRequiredSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); | ||
|
||
var diagnostic = context.Diagnostics[0]; | ||
|
||
// Get the parameter symbol | ||
var parameterSymbol = semanticModel.GetDeclaredSymbol(node, context.CancellationToken) as IParameterSymbol; | ||
if (parameterSymbol == null) | ||
{ | ||
return; | ||
} | ||
|
||
// Determine the target type name from the diagnostic properties or compute it | ||
var targetTypeName = GetReadOnlyTypeName(parameterSymbol.Type); | ||
if (targetTypeName == null) | ||
{ | ||
return; | ||
} | ||
stephentoub marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
||
var title = string.Format(MicrosoftNetCoreAnalyzersResources.PreferReadOnlySpanOverSpanCodeFixTitle, targetTypeName); | ||
|
||
context.RegisterCodeFix( | ||
CodeAction.Create( | ||
title: title, | ||
createChangedDocument: c => ChangeParameterTypeAsync(context.Document, node, targetTypeName, c), | ||
equivalenceKey: title), | ||
diagnostic); | ||
} | ||
|
||
private static string? GetReadOnlyTypeName(ITypeSymbol typeSymbol) | ||
{ | ||
if (typeSymbol is not INamedTypeSymbol namedType) | ||
{ | ||
return null; | ||
} | ||
|
||
var typeName = namedType.OriginalDefinition.Name; | ||
if (typeName == "Span" && namedType.TypeArguments.Length == 1) | ||
{ | ||
return $"ReadOnlySpan<{namedType.TypeArguments[0].ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)}>"; | ||
} | ||
else if (typeName == "Memory" && namedType.TypeArguments.Length == 1) | ||
{ | ||
return $"ReadOnlyMemory<{namedType.TypeArguments[0].ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)}>"; | ||
} | ||
stephentoub marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
||
return null; | ||
} | ||
|
||
private static async Task<Document> ChangeParameterTypeAsync( | ||
Document document, | ||
SyntaxNode node, | ||
string newTypeName, | ||
stephentoub marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
CancellationToken cancellationToken) | ||
{ | ||
var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); | ||
var generator = editor.Generator; | ||
var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); | ||
|
||
// Get the parameter symbol to construct the correct type | ||
var parameterSymbol = semanticModel.GetDeclaredSymbol(node, cancellationToken) as IParameterSymbol; | ||
if (parameterSymbol?.Type is not INamedTypeSymbol namedType || namedType.TypeArguments.Length != 1) | ||
{ | ||
return document; | ||
} | ||
|
||
// Get the compilation to find the readonly types | ||
var compilation = semanticModel.Compilation; | ||
var typeName = namedType.OriginalDefinition.Name; | ||
INamedTypeSymbol? readOnlyType = null; | ||
|
||
if (typeName == "Span") | ||
{ | ||
readOnlyType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlySpan1); | ||
} | ||
else if (typeName == "Memory") | ||
{ | ||
readOnlyType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlyMemory1); | ||
} | ||
|
||
if (readOnlyType == null) | ||
{ | ||
return document; | ||
} | ||
|
||
// Construct the generic type with the same type argument | ||
var newType = readOnlyType.Construct(namedType.TypeArguments[0]); | ||
var newTypeNode = generator.TypeExpression(newType); | ||
|
||
// Replace the parameter's type | ||
editor.ReplaceNode(node, (currentNode, gen) => | ||
{ | ||
return gen.WithType(currentNode, newTypeNode); | ||
}); | ||
|
||
return editor.GetChangedDocument(); | ||
} | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.