Skip to content
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
Expand Up @@ -948,6 +948,18 @@ This rule detects usage of platform-specific intrinsics that can be replaced wit
|CodeFix|True|
---

## [CA1517](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1517): Use 'ReadOnlySpan\<T>' or 'ReadOnlyMemory\<T>' instead of 'Span\<T>' or 'Memory\<T>'

Using 'ReadOnlySpan\<T>' or 'ReadOnlyMemory\<T>' instead of 'Span\<T>' or 'Memory\<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance.

|Item|Value|
|-|-|
|Category|Maintainability|
|Enabled|True|
|Severity|Info|
|CodeFix|True|
---

## [CA1700](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1700): Do not name enum values 'Reserved'

This rule assumes that an enumeration member that has a name that contains "reserved" is not currently used but is a placeholder to be renamed or removed in a future version. Renaming or removing a member is a breaking change.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2182,6 +2182,26 @@
]
}
},
"CA1517": {
"id": "CA1517",
"shortDescription": "Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>'",
"fullDescription": "Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance.",
"defaultLevel": "note",
"helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1517",
"properties": {
"category": "Maintainability",
"isEnabledByDefault": true,
"typeName": "PreferReadOnlySpanOverSpanAnalyzer",
"languages": [
"C#",
"Visual Basic"
],
"tags": [
"Telemetry",
"EnabledRuleInAggressiveMode"
]
}
},
"CA1700": {
"id": "CA1700",
"shortDescription": "Do not name enum values 'Reserved'",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
CA1517 | Maintainability | Info | PreferReadOnlySpanOverSpanAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/CA1516)
CA1873 | Performance | Info | AvoidPotentiallyExpensiveCallWhenLoggingAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873)
CA1874 | Performance | Info | UseRegexMembers, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1874)
CA1875 | Performance | Info | UseRegexMembers, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1875)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2261,4 +2261,16 @@ Widening and user defined conversions are not supported with generic types.</val
<data name="CollapseMultiplePathOperationsCodeFixTitle" xml:space="preserve">
<value>Collapse into single Path.{0} operation</value>
</data>
<data name="PreferReadOnlySpanOverSpanTitle" xml:space="preserve">
<value>Use 'ReadOnlySpan&lt;T&gt;' or 'ReadOnlyMemory&lt;T&gt;' instead of 'Span&lt;T&gt;' or 'Memory&lt;T&gt;'</value>
</data>
<data name="PreferReadOnlySpanOverSpanMessage" xml:space="preserve">
<value>Parameter '{0}' can be declared as '{1}' instead of as '{2}'</value>
</data>
<data name="PreferReadOnlySpanOverSpanDescription" xml:space="preserve">
<value>Using 'ReadOnlySpan&lt;T&gt;' or 'ReadOnlyMemory&lt;T&gt;' instead of 'Span&lt;T&gt;' or 'Memory&lt;T&gt;' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance.</value>
</data>
<data name="PreferReadOnlySpanOverSpanCodeFixTitle" xml:space="preserve">
<value>Change to '{0}'</value>
</data>
</root>
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// 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 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>
/// CA1517: Use ReadOnlySpan&lt;T&gt; or ReadOnlyMemory&lt;T&gt; instead of Span&lt;T&gt; or Memory&lt;T&gt;
/// </summary>
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(PreferReadOnlySpanOverSpanFixer))]
[Shared]
public sealed class PreferReadOnlySpanOverSpanFixer : CodeFixProvider
{
public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } =
ImmutableArray.Create(PreferReadOnlySpanOverSpanAnalyzer.RuleId);

public sealed override FixAllProvider GetFixAllProvider() => 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);

if (semanticModel.GetDeclaredSymbol(node, context.CancellationToken) is IParameterSymbol parameterSymbol &&
GetReadOnlyTypeName(parameterSymbol.Type) is { } targetTypeName)
{
var title = string.Format(MicrosoftNetCoreAnalyzersResources.PreferReadOnlySpanOverSpanCodeFixTitle, targetTypeName);

context.RegisterCodeFix(
CodeAction.Create(
title: title,
createChangedDocument: c => ChangeParameterTypeAsync(context.Document, node, c),
equivalenceKey: title),
context.Diagnostics[0]);
}
}

private static string? GetReadOnlyTypeName(ITypeSymbol typeSymbol) =>
typeSymbol is INamedTypeSymbol namedType && namedType.OriginalDefinition.Name is "Span" or "Memory" ?
$"ReadOnly{typeSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)}" :
null;

private static async Task<Document> ChangeParameterTypeAsync(
Document document,
SyntaxNode node,
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 INamedTypeSymbol namedType && namedType.TypeArguments.Length == 1)
{
// Get the compilation to find the readonly types
var compilation = semanticModel.Compilation;
var typeName = namedType.OriginalDefinition.Name;

INamedTypeSymbol? readOnlyType =
typeName is "Span" ? compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlySpan1) :
typeName is "Memory" ? compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlyMemory1) :
null;

if (readOnlyType is not null)
{
// Construct the generic type with the same type argument
var newTypeNode = generator.TypeExpression(
readOnlyType.Construct(namedType.TypeArguments[0]));

// Replace the parameter's type
editor.ReplaceNode(node, (currentNode, gen) => gen.WithType(currentNode, newTypeNode));

return editor.GetChangedDocument();
}
}

return document;
}
}
}
Loading
Loading